SpringCloud详解
文章目录
- 微服务解决方案
- SpringCloud体系
- Eureka
- Hystrix
- Gateway
- OpenFeign
- Ribbon
- Config
微服务解决方案
随着微服务模式的使用,服务之间的调用带来的问题有很多,如数据一致性、网络波动、缓存、事务等问题。根据服务治理的思想,并针对这一系列的问题,目前主流一站式微服务解决方案有SpringCloud
、SpringCloud Alibaba
及以Dubbo
构成的体系。
功能模块 | Spring Cloud | Dubbo | Spring Cloud Alibaba |
---|---|---|---|
服务注册与发现 | Eureka、Consul、Zookeeper | Zookeeper、Nacos | Nacos |
熔断与限流 | Resilience4j、Hystrix(已停更,逐步替换) | 无(需外部集成) | Sentinel(限流、熔断、降级) |
负载均衡 | Ribbon、Spring Cloud LoadBalancer、Feign | Dubbo | Nacos、Spring Cloud LoadBalancer |
配置管理 | Spring Cloud Config、Consul | 无(可与 Nacos、Apollo 集成) | Nacos |
API 网关 | Spring Cloud Gateway、Zuul(已停更) | 无(需外部集成) | Spring Cloud Gateway |
分布式追踪 | Spring Cloud Sleuth + Zipkin / Jaeger | Skywalking、Zipkin | Spring Cloud Sleuth + Zipkin / Skywalking |
服务调用 | Feign、RestTemplate | Dubbo | Feign、RestTemplate、Dubbo |
分布式事务 | Seata、TCC-Transaction、SAGA | TCC、XA 等协议 | Seata(支持 AT、TCC、SAGA 模式) |
通信协议 | HTTP/REST、gRPC(需扩展支持) | Dubbo 协议(基于高效 RPC 调用) | HTTP/REST、Dubbo 协议 |
适用场景 | 面向全栈微服务治理,适合通用微服务架构 | 高性能 RPC 调用,适合低延迟、高并发场景 | 增强版 Spring Cloud,适合国内复杂分布式场景 |
生态成熟度 | 完善,社区活跃 | 成熟,广泛应用于国内企业 | 活跃,逐渐成为国内微服务治理首选 |
SpringCloud体系
SpringCloud
它并不是一个框架,而是很多个框架。它是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶。
Eureka
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers。 We call this service, the Eureka Server。 Eureka also comes with a Java-based client component,the Eureka Client, which makes interactions with the service much easier。 The client also has a built-in load balancer that does basic round-robin load balancing。 —https://github.com/Netflix/eureka
大意:Eureka是一个REST (Representational State Transfer)服务,它主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移,我们称此服务为Eureka服务器。Eureka也有一个基于java的客户端组件,Eureka客户端,这使得与服务的交互更加容易,同时客户端也有一个内置的负载平衡器,它执行基本的循环负载均衡。
Eureka采用了Client / Server 的设计架构,提供了完整的服务注册和服务发现,可以和 SpringCloud 无缝集成。Eureka Sever作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
- Eureka Server 提供服务注册服务;各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
- Eureka Client 通过注册中心进行访问;它是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的使用轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳默认周期为30秒。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除,默认90秒。
- Service Provider:服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到;
- Service Consumer:服务消费方,从Eureka获取注册服务列表,从而能够消费服务;
默认使用下,Eureka有一个自我保护机制,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制:
- Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务;
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用;
- 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中;
简而言之,某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。所以Eureka属于AP。
Hystrix
当微服务系统的一个服务出现故障时,故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,这就是“雪崩效应”。为了防止此类事件的发生,微服务架构引入了“断路器”的一系列服务容错和保护机制。
"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
SpringCloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。SpringCloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
熔断机制是为了应对雪崩效应而出现的一种微服务链路保护机制。当微服务系统中的某个微服务不可用或响应时间太长时,为了保护系统的整体可用性,熔断器会暂时切断请求对该服务的调用,并快速返回一个友好的错误响应。这种熔断状态不是永久的,在经历了一定的时间后,熔断器会再次检测该微服务是否恢复正常,若服务恢复正常则恢复其调用链路。
在熔断机制中涉及了三种熔断状态:
- 熔断打开:请求不再进行调用当前服务,内部定时一般为MTTR(平均故障处理时间),当打开时长达到所设的时长则进入半熔断状态;
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断;
- 熔断关闭:熔断关闭不会对服务进行熔断;
Hystrix 实现服务熔断的步骤如下:
- 当服务的调用出错率达到或超过 Hystix 规定的比率(默认为 50%)后,熔断器进入熔断开启状态。
- 熔断器进入熔断开启状态后,Hystrix 会启动一个休眠时间窗,在这个时间窗内,该服务的降级逻辑会临时充当业务主逻辑,而原来的业务主逻辑不可用。
- 当有请求再次调用该服务时,会直接调用降级逻辑快速地返回失败响应,以避免系统雪崩。
- 当休眠时间窗到期后,Hystrix 会进入半熔断转态,允许部分请求对服务原来的主业务逻辑进行调用,并监控其调用成功率。
- 如果调用成功率达到预期,则说明服务已恢复正常,Hystrix 进入熔断关闭状态,服务原来的主业务逻辑恢复;否则 Hystrix 重新进入熔断开启状态,休眠时间窗口重新计时,继续重复第 2 到第 5 步。
Hystrix工作流程:
- 创建
HystrixCommand
或HystrixObserableCommand
对象。 - 命令执行:
- 其中
HystrixCommand
实现了下面前两种执行方式:execute
:同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常。queue
:异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
HystrixObservableCommand
实现了后两种执行方式:obseve()
:返回Observable对象,它代表了操作的多个结果,它是一个Hot Observable,不论“事件源”是否有“订阅者”,都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程。toObservable()
:同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable,没有“订间者”的时候并不会发布事件,而是进行等待,直到有“订阅者"之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程。
- 其中
- 判断当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
- 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
- 判断线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。
- Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务:
HystrixCommand.run()
:返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct()
:返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
- Hystrix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。
- 当命令执行失败的时候,Hystrix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
- 第4步∶当前命令处于“熔断/短路”状态,断路器是打开的时候。
- 第5步∶当前命令的线程池、请求队列或者信号量被占满的时候。
- 第6步∶
HystrixObsevableCommand.construct()
或HytrixCommand.run()
抛出异常的时候。
- 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回给调用方。
Gateway
SpringCloud Gateway 属于 SpringCloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 Zuul。准确点来说,应该是 Zuul 1.x。SpringCloud Gateway 起步要比 Zuul 2.x 更早。为了提升网关的性能,SpringCloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现同步非阻塞的 I/O。
Gateway 核心组件:
- Route:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
- Predicate:断言参考的是Java8的
java.util.function。Predicate
,开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数,如果请求与断言相匹配则进行路由; - Filter:过滤指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改;
工作流程:
- 路由判断:客户端的请求到达网关后,先经过 Gateway Handler Mapping 处理,这里面会做断言(Predicate)判断,看下符合哪个路由规则,这个路由映射后端的某个服务。
- 请求过滤:然后请求到达 Gateway Web Handler,这里面有很多过滤器,组成过滤器链(Filter Chain),这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等等,有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器逻辑上可以称作 Pre-Filters,Pre 可以理解为“在…之前”。
- 服务处理:后端服务会对请求进行处理。
- 响应过滤:后端处理完结果后,返回给 Gateway 的过滤器再次做处理,逻辑上可以称作 Post-Filters,Post 可以理解为“在…之后”。
- 响应返回:响应经过过滤处理后,返回给客户端。
客户端的请求先通过匹配规则找到合适的路由,映射到具体的服务,然后请求经过过滤器处理后转发给具体的服务,服务处理后,再次经过过滤器处理,最后返回给客户端。
对于公司业务以 Java 为主要开发语言的情况下,SpringCloud Gateway 通常是个不错的选择,其优点有,简单易用、成熟稳定、与 SpringCloud 生态系统兼容、Spring 社区成熟等等。不过,SpringCloud Gateway 也有一些局限性和不足之处, 一般还需要结合其他网关一起使用比如 OpenResty。并且,其性能相比较于 Kong 和 APISIX,还是差一些。如果对性能要求比较高的话,SpringCloud Gateway 不是一个好的选择。
OpenFeign
Feign is a declarative web service client。 It makes writing web service clients easier。 To use Feign create an interface and annotate it。 It has pluggable annotation support including Feign annotations and JAX-RS annotations。 Feign also supports pluggable encoders and decoders。 Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web。 Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign。
大意:Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign与OpenFeign:
- Feign是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务;
- OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如
@RequesMapping
等等。OpenFeign的@Feignclient
可以解析SpringMVc的@RequestMapping
注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务;
通过使用OpenFeign
可以使代码变得更加简洁,减轻程序员负担:
// 伪代码
@Component
// 括号中,为在注册中心注册的服务名称
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPayment(@PathVariable("id") Long id);
@PostMapping("/payment/timeout")
String feignTimeoutTest();
}
OpenFeign工作原理:
- 使用注解
@FeignClient
注册FactoryBean
到IOC容器, 最终产生了一个虚假的实现类代理; - 使用Feign调用接口的地方,最终拿到的都是一个假的代理实现类;
- 所有发生在代理实现类上的调用,都被转交给Feign框架,翻译成HTTP的形式发送出去,并得到返回结果,再翻译回接口定义的返回值形式;
Ribbon
SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具,是一个内置软件负载平衡器的进程间通信(远程过程调用)库。主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如连接超时、重试等。
本地负载与服务端负载:
- Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的;
- Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术;
Ribbon其实是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,例如与Eureka结合:
消费方和服务方在注册中心注册服务,当消费方发起请求时,Ribbon会去注册中心寻找请求的服务名,即服务方集群,Ribbon默认负载算法会根据接口第几次请求 % 服务器集群总数量
算出实际消费方服务器的位置,每次服务重启动后rest接口计数从1开始。
模拟Ribbon默认负载均衡算法:
public interface ILoadBalance {
ServiceInstance instance(List<ServiceInstance> instances);
}
@Component
public class MyLoadBalance implements ILoadBalance{
/**
* 轮询索引
*/
private final AtomicInteger index = new AtomicInteger(0);
/**
* 负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
* @param instances 服务器集群数量
* @return 实际服务器的下标
*/
@Override
public ServiceInstance instance(List<ServiceInstance> instances) {
return instances.get(incrementAndGet() % instances.size());
}
public final int incrementAndGet() {
int current = 0;
int next = 0;
do {
current = index.get();
// 当最大数量超过 Integer。MAX_VALUE 归0
next = current >= 2147483647 ? 0 : current + 1;
}while (!index.compareAndSet(current, next));
return next;
}
}
@Resource
private RestTemplate restTemplate;
@Resource
private DiscoveryClient discoveryClient;
@Resource
private ILoadBalance iLoadBalance;
@GetMapping("/myLoadBalance")
public String myLoadBalanceTest() {
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
ServiceInstance instance = iLoadBalance.instance(instances);
URI uri = instance.getUri();
String finalUri = String.format("%s/%s",uri, PaymentConstant.PAYMENT_GETPORT_API);
log.info("自定义负载均衡,请求地址:{}", finalUri);
return restTemplate.getForObject(finalUri, String.class);
}
Config
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config是配置中心的一种,它分为服务端和客户端两部分:
- 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口;Config-Server端集中式存储/管理配置文件,并对外提供接口方便Config-Client访问,接口使用HTTP的方式对外提供访问;
- 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容;
使用SpringCloud Config非常简单,需要在服务端和客户端分别进行改造:
- 引入依赖就不说了,首先将分布式中系统中的配置放到git或svn上,在服务端配置文件里配置好git或svn用户名密码、配置文件所在的分支及配置文件的目录,再将服务端启动类加上
@EnableConfigServer
注解,就大功告成了; - 放在git或svn上的配置文件名称要符合规则,才能通过配置中心访问得到该文件。一些常用的配置文件规则,其中,
label:分支
、profiles:环境(dev/test/prod)
、application:服务名
:## http://127.0.0.1:3344/master/config-dev.yml 1. /{label}/{application}-{profile}.yml ## http://127.0.0.1:3344/config-dev.yml 2. /{application}-{profile}.yml ## http://127.0.0.1:3344/config/dev/master 3. /{application}/{profile}[/{label}]
- 在客户端也要引入依赖和进行相关配置:配置中心地址、分支名称、配置文件名称、环境。需要注意的是要将客户端模块下的
application.yml
文件改为bootstrap.yml
,再将主启动类加上@EnableEurekaClient
注解;当配置客户端启动时,它绑定到配置服务器(通过spring.cloud.config.uri引导配置属性),并使用远程属性源初始化Spring Environment。
这种行为的最终结果是,所有想要使用Config Server的客户端应用程序都需要bootstrap.yml或一个环境变量applicaiton.yml是用户级的资源配置项,而bootstrap.yml是系统级的,优先级更加高,在加载配置时优先加载bootstrap.yml
当将SpringCloud Config客户端服务端都配置好之后,修改配置时会发现修改的配置文件不能实时生效。针对这个问题,可以将服务重启或者调用actuator
的刷新接口使其生效,使用之前需要引入actuator
的依赖:
# 使用SpringCloud Config修改完配置后,调用刷新接口使客户端配置生效
curl -X POST "http://localhost:3355/actuator/refresh"
为了避免手动的调用刷新接口,可以使用SpringCloud Bus
配合SpringCloud Config
实现配置的动态刷新。