书籍地址: Spring Cloud 微服务架构开发实战
在微服务场景下,随着业务的发展,原来简单的系统会变复杂,微服务的数量会不断增加. 每个微服务都会对外暴露一组粒度不同的服务,客户端想完成一个功能时需要和众多的微服务打交道,甚至加载一个页面也需要调用一系列的服务接口才能完成,这样不但加重了客户端的负担,而且对基于互联网的访问也会造成性能低下.
同时,由于需要访问多个微服务,增加客户端的复杂度,而且客户端还需要指导各个微服务之间的关系,一个某个微服务更改就会造成前端噩梦.
因此对于基于微服务的应用,客户端和微服务之间的交互面临如下挑战:
需要解决微服务提供的API粒度与客户端需求的匹配问题. 通常微服务提供的API是细粒度的,而客户端往往需要众多这样的API才可以完成一个功能的处理,从而迫使客户端要与多个微服务进行频繁的交互。客户端的多样性,造成了在处理时需要提供不同的数据,加上客户端网络差异的影响,即使一个简单的功能,如果放在互联网应用中需要考虑的就不仅仅是功能的实现了。随着业务的演进,微服务的划分随时都可能会发生变化,如果不能对客户端隐藏这些细节,就会面临牵一发而动全身的困境。针对上述挑战,不知各位有何良策应对呢?
笔者觉得,可在客户端和微服务之间加一层 saas 统一为客户端提供接口。 复杂的请求多个微服务调用的动作由saas完成。 saas也是个微服务只不过是单纯的消费者。
而为了解决上述问题,其实早已经有成熟的解决方案,那就是 API服务网关(API Gateway)
API服务网关: 就是出现在微服务边界上的一个面向API的、串行集中式的、对访问请求强管控的服务,采用的是一个外观模式。
API服务网关是微服务访问的统一入口,负责服务请求路由、组合及协议转换等处理。
API服务网关的核心是: 为所有客户端请求或其它消费者提供统一的网关,通过该网关接入不同的微服务,并隐藏架构实现的细节。
对于API服务网关,我们总结如下:
API 服务网关帮助开发者隐藏系统架构实现的细节,提供统一的入口供客户端访问,让微服务使用更为友好。通过微服务的同意访问控制,简化了客户端开发的复杂度,降低了客户端于微服务之间的通信次数,客户端不需要与多个微服务之间进行通信,也不需要了解各个微服务的详细信息。借助API服务网关可统一做切面任务,避免每个微服务自己开发,提升效率,是系统更加标准化。通过API服务网关,可以将异构系统进行统一整合。API服务网关需要实现一个高可用伸缩性强的服务,避免单点失效。API服务网关需要对所有微服务实例所暴露的端点进行统一的管理,这为开发和运维增加了一定的难度。Zuul 是 Netflix 框架中的 高可用伸缩性强的 API服务网关 的服务组件;
其功能有二: 功能1:路由 功能2:过滤器
Zuul 是一个基于JVM路由和服务端的负载均衡器
其参考GOF设计模式中的外观模式,将细粒度的服务组合起来提供了一个粗粒度的服务,以便所有的请求都导入一个统一的入口,整个服务只需要暴露一个API,对外屏蔽了服务端的实现细节;
Netflix Zuul 组件可以用于反向代理功能,通过路由寻址请求转发到后端的服务上,并增加一些通用逻辑处理。
Zuul对请求提供了路由和过滤器两个功能,其中,路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
通过Zuul组件,可以完成以下功能:
动态路由
Zuul 路由服务器支持与 Eureka 服务器的整合,可以动态对注册到 Eureka 服务器中的微服务进行路由映射。另外,Zuul 提供一系列的路由规则配置,可以针对生产中的实际情况进行配置,实现微服务路由的灵活控制;
监控与审查
通过对一些特定的接口设置访问白名单、访问次数、访问频率等各类设置,可以在不影响微服务实现的情况下,对访问实时监控和审查处理;
身份认证与安全
通过 Zuul 可以将认证的部分单独抽取出来,让微服务系统无须关注认证的逻辑,只需要关注业务本身即可。
可以统一在服务网关层增加一个额外的保护层来防止恶意攻击,如果客户端直连微服务的化,则每个暴露的微服务都需要面临这个安全问题;
压力测试
通过 Zuul 所提供的过滤功能可以逐渐增加对某一服务集群的流量,以了解服务性能,从而及早对运维架构做出调优。
金丝雀、AB测试
新版本、新功能可能都需要测试用户对其的反应,通过API服务网关,可以轻松控制部分用户访问服务实例,并且可以对用户行为进行记录和分析,以便对新版本及新功能进行评价,获取应用的最优方案。
服务迁移
通过 Zuul 代理可以处理来自旧端点的客户端上的所有流量,将一些请求重定向到新的端点,从而慢慢地用不同地实现来替换旧端点。
负载剪裁/限流
为每一个负载类型分配对应的容量,对超过限定值的请求弃用,这样可以防止站点不被未知的大流量冲垮。通常,可以利用 API服务网关配置一个阈值,当请求数超过该阈值时会直接返回错误。
Spring Cloud 提供的 @EnableZuulProxy注解可以用来创建 Zuul 路由服务器,所以创建的路由服务器可以是嵌入式服务,也可以进行独立部署。
1. 编写pom.yml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.turnsole</groupId> <artifactId>shop-zuul</artifactId> <version>0.0.1</version> <name>shop-zuul</name> <description>shop-zuul project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--zuul--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>2. 编写启动类
@EnableZuulProxy @SpringBootApplication public class ShopZuulApplication { public static void main(String[] args) { SpringApplication.run(ShopZuulApplication.class, args); } }很明显,重点是 @EnableZuulProxy,它用来告诉应用,启动运行Zuul服务,并根据配置进行响应的初始化处理。
@EnableZuulProxy,包含@EnableCircuitBreaker和@EnableDiscoveryClient注解。 就是说,当使用了@EnableZuulProxy注解,就自动为该应用增加了服务的容错保护功能,同时也将@EnableZuulProxy注解走位一个服务注册到服务治理服务器中。
3. 编写配置文件
server: port: 8280 spring: application: name: ZUUL-PROXY eureka: client: service-url: defaultZone: http://localhost:8761/eurekaattention1: 对于微服务模式的开发, 动辄会有十几个甚至几十个微服务, 因此对于 IP 地址及端口号的分配也非常重要, 越早规范越能够避免后续的运维压力.
比如: 服务治理服务器的端口号为默认端口8761, 各业务服务器端口号从2100开始,各业务端口按100进行递增, 一个业务微服务的服务器端口按 10 进行递增
attention2:
这里先把一个在下看书过程中,遇到的书中未提及的错误摆出来,可以先不解决,直接进行下面的测试环节,先遇上看一下这个问题
问题描述:
com.netflix.zuul.exception.ZuulException: Hystrix Readed time out出现上述问题的原因是:zuul的默认超时时间比较小 因此我们需要为zuul设置一组超时时间,因zuul启用了ribbon的负载均衡,还需要设置ribbon的超时时间,注意ribbon的超时时间要小于zuul超时时间。
zuul: host: connect-timeout-millis: 15000 #HTTP连接超时要比Hystrix的大 socket-timeout-millis: 60000 #socket超时 ribbon: ReadTimeout: 10000 ConnectTimeout: 10000这里pom文件不需要额外引入feign或ribbon的依赖,并且在上述ribbon的相关设置中是不会提醒的,但是没关系,依然可以使用,注意:后面的空格
4. 路由测试
启动 service-discovery启动 user-service启动 product-service启动 shop-zuulpostman 访问url http://localhost:8280/productservice/comment/2/comments得到的响应结果,如果配置了上面的超时时间,那么是正常响应结果的这说明Zuul路由服务器已经将请求自动转发到商品服务微服务中。您也可以通过zuul路由服务器访问用户接口,其实也可以正常响应
Zuul路由服务器的默认设计就是能够与SpringCloud相关产品进行整合,Zuul 将默认从Eureka服务器中获取所注册的服务,然后将服务的 ID 作为请求路径中的一部分,然后将用户的请求自动转发的这些服务中
5. 负载均衡测试
我们先把用户微服务打一个jar包,赋予其端口号为2110,通过 java -jar 将其启动java -jar user-service-0.0.1.jar --server.port=2110 通过postman访问之前访问的接服务地址 http://localhost:8280/productservice/comment/2/comments可以看到两个用户微服务,会被交替访问
注意:这里涉及到了Zuul 负载均衡的权重问题,因此在测试的时候会有访问同一个微服务的情况,我们在后面会进行学习。
我们要知道 Zuul 路由服务可能会成为另一个访问瓶颈.
需要把握以下两个原则:
KISS原则stateless原则KISS原则 Keep it Simple and Stupid 保持 API 服务网关的简单和轻量. 服务网关只不过是服务调用过程中的一个检查点, 不应该用来处理业务的复杂性, 也不应该将其用于解决架构的难点上, 这应该是微服务需要处理的事情.
stateless原则 stateless原则是指在 Zuul 服务网关中不应该、也不可以保存有关服务调用过程中的状态数据。
6. Zuul中的Hystrix容错
Zuul 路由服务已经默认整合了 Hystrix。现在看一下 @EnableZuulProxy 注解的源码:
@EnableCircuitBreaker @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Import({ZuulProxyMarkerConfiguration.class}) public @interface EnableZuulProxy { }可以看到 Zuul 本身已经默认集成了 Hystrix 和 Ribbon,所以 Zuul 天生就拥有线程隔离和服务容错的自我保护能力,以及对服务调用的客户端负载均衡功能。
注意:当使用path 与 url的映射关系来配置路由规则时,对于路由转发的请求则不会采用 Hystrix Command 来包装,所以这类路由请求请求就没有线程隔离和服务容错保护功能,并且也不会有负载均衡能力。 因此在使用 Zuul 的时候尽量使用 path 和 serviceId 的组合进行配置,这样不仅可以保证 API 网关的健壮和稳定,也能用到 Ribbon 的客户端负载均衡功能。