Spring Cloud 概述
简介
Spring Cloud是一个基于 Spring Boot实现的微服务架构开发框架。它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品,还可能会新增),如下所述。
-
Spring Cloud Config:配置管理工具,支持使用Git存储配置内容,可以使用它实现应用配置的外部化存储,并支持客户端配置信息刷新、加密/解密配置内容等。
-
Spring Cloud Netflix:核心组件,对多个 Netflix OSS开源套件进行整合
- Eureka:服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
- Ribbon:客户端负载均衡的服务调用组件。
- Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
- Feign:基于 Ribbon和Hystrix的声明式服务调用组件。
- Zuul:网关组件,提供智能路由、访问过滤等功能。
-
Spring Cloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来动态刷新配置等。
版本说明
Spring Cloud不像 Spring社区其他一些项目那样相对独立,它是一个拥有诸多子项目的大型综合项目,可以说是对微服务架构解决方案的综合套件组合,其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着自己的发布版本号。因此每一个Spring Cloud的版本都会包含多个不同版本的子项目,为了管理每个版本的子项目清单,避免 Spring Cloud的版本号与其子项目的版本号相混淆,没有采用版本号的方式,而是通过命名的方式。
这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来对应版本时间顺序,比如最早的 Release版本为 Angel,第二个 Release版本为 Brixton
当一个版本的 Spring Cloud项目的发布内容积累到临界点或者一个严重bug解决可用后,就会发布一个“service releases”版本,简称SRX版本,其中X是一个递增的数字,所以 Brixton.SR5就是 Brixton的第5个 Release版本
Spring Cloud Version | Spring Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
springcloud和SpringBoot的区别:
-
作用不同:
- SpringBoot是一个快速开发框架,使用注解简化了xml配置,内置了Servlet容器,以Java应用程序进行执行,作用是为了提供默认配置,从而简化配置过程
- SpringCloud是基于SpringBoot的一系列框架的集合,作用是为了给微服务提供一个综合管理框架
-
使用方式不同:
- SpringBoot可以单独使用
- SpringCloud必须在SpringBoot使用的前提下才能使用。
-
SpringBoot和SpringCloud都是从spring生态圈中衍生出来的软件开发框架,但是二者的创作初衷是完全不同的:
- SpringBoot的设计目的是为了在微服务开发过程中可以简化配置文件,提高工作效率
- SpringCloud的设计目的是为了管理同一项目中的各项微服务
因此二者是完全不同的两个软件开发框架。
负载均衡 Ribbon
Ribbon是一个客户端负载均衡工具
实际环境中微服务一般是集群部署,服务列表中实例会有多个,这种情况下就需要编写负载均衡算法,在多个实例列表中进行选择。
Ribbon配置项
全局配置 使用 ribbon.=
ribbon:
ReadTimeout: 2500 # 数据通信超时时长,单位:ms。默认为1000
ConnectTimeout: 500 # 连接超时时长,单位:ms。默认为1000
OkToRetryOnAllOperations: false # 是否对所有的异常请求(连接异常和请求异常)都重试。默认为false
MaxAutoRetriesNextServer: 1 # 最多重试多少次连接服务(实例)。默认为1。不包括首次调用
MaxAutoRetries: 0 # 服务的单个实例的重试次数。默认为0。不包括首次调用
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 切换负载均衡策略为随机。默认为轮询策略
指定服务配置 <服务名称>.ribbon. =
serverName: # 单独给某⼀服务配置。serverName是服务名,使⽤的时候要⽤服务名替换掉这个
ribbon:
connectTimeout: 5000
readTimeout: 5000
入门案例
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
-
在RestTemplate的配置方法上添加 @LoadBalanced注解:
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
-
直接通过服务名称调用
@GetMapping("user/{id}") public User getUserById(@PathVariable long id) { String url = "http://user-service" + "/user/" + id; User user = restTemplate.getForObject(url, User.class); return user; }
负载均衡原理
- 在RestTemplate上添加了@LoadBalanced注解后,会使用LoadBalancerClient来配置RestTemplate
- Spring Cloud Ribbon 的自动配置类LoadBalancerAutoConfiguration中的@ConditionalOnBean(LoadBalancerClient.class)条件成立
- 自动配置中添加了LoadBalancerInterceptor,这个拦截器会拦截请求,通过服务ID获取服务的地址列表,然后通过负载均衡算法选出一个地址进行调用
服务容错 Hystrix
概念
服务故障雪崩效应:
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + LoadBalanceClient 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的雪崩效应。
服务降级:
在分布式系统中,但多个服务之间调用时发生一些“错误”,如果不能对这些“错误”进行友好的处理,就会导致整个项目瘫痪,这是万万不能发生的。所以需要进行服务降级来解决这个问题,类似于try-catch机制,发生错误了,就执行catch中的代码。
当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
服务熔断:
一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。
在分布式系统中,就是调用一个系统时,在一定时间内,调用这个服务发生的错误次数达到一定的值时, 就打开断路器,不再调用过去,而是直接去调用降级方法。再过一段时间后,当一次调用时,发现这个服务通了,就将这个断路器改为“半开”状态,让调用一个一个的慢慢过去,如果一直没有发生错误,就将这个断路器关闭,让所有的服务全部通过。
适用场景:防止应用程序直接调用那些很可能会调用失败的远程服务或共享资源
Hystrix
Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败
Hystix解决雪崩问题的手段主要包括:
- 线程隔离
- 服务熔断
Hystrix配置项
hystrix:
command:
default: # 全局默认配置
execution: # 线程隔离相关
timeout:
enabled: true # 是否给方法执行设置超时时间,默认为true。一般不改。
isolation:
strategy: THREAD # 配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore,
thread:
timeoutlnMilliseconds: 10000 # 方式执行的超时时间,默认为1000毫秒,在实际场景中需要根据情况设置
circuitBreaker: # 服务熔断相关
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
sleepWindowInMilliseconds: 10000 # 休眠时长,单位毫秒,默认是5000毫秒
errorThresholdPercentage: 50 # 触发熔断的失败请求最小占比,默认50%
serverName: # 单独给某⼀服务配置
execution:
timeout:
enabled: true
isolation:
strategy: THREAD
thread:
timeoutlnMilliseconds: 10000
线程隔离
原理
Hystrix为每个依赖服务调用分配一个小的独立线程池,用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满调用将被立即拒绝,否则使用线程来处理请求,可以在主线程中设置超时时间,超过这个时间如果子线程还没有执行完成任务或者子线程执行出现异常,则会进行服务降级。
入门案例
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
在启动类上添加注解:
@EnableCircuitBreaker
,开启熔断注:组合注解@SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
// @SpringBootApplication // @EnableDiscoveryClient // @EnableCircuitBreaker @SpringCloudApplication public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
编写降级逻辑
当目标服务的调用出现故障,返回一个错误响应。需要提前编写好失败时的降级处理逻辑,并使用注解@HystixCommond指定降级处理方法
@GetMapping("user/{id}") @HystrixCommand(fallbackMethod = "getUserByIdFallback") // 指定降级处理方法 public User getUserById(@PathVariable long id) { // 使用服务ID替换真实地址 String url = "http://user-service" + "/user/" + id; User user = restTemplate.getForObject(url, User.class); return user; } /** * 降级逻辑处理方法,方法参数、返回类型与原始方法一致 */ private User getUserByIdFallback(long id) { User user = new User(1L,"我是备份",18,new Date()); return user; }
-
测试结果:
-
当被调用服务正常提供服务时,访问与以前一致。
-
当被调用服务停机时,调用返回了降级逻辑处理方法的返回值
-
默认的Fallback
可以使用注解@DefaultProperties把Fallback配置加在类上,实现默认fallback
- 默认方法返回类型必须与调用方法一致,并且不能有参数
@RestController
@RequestMapping("/consumer")
@DefaultProperties(defaultFallback = "defaultFallback") // 默认的Fallback
public class ConsumerController {
@Autowired // 注入RestTemplate
private RestTemplate restTemplate;
@GetMapping("user/{id}")
@HystrixCommand
public User getUserById(@PathVariable long id) {
// 使用服务ID替换真实地址
String url = "http://user-service" + "/user/" + id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
/**
* 降级逻辑处理方法,方法参数、返回类型与原始方法一致
*/
private User getUserByIdFallback(long id) {
User user = new User(1L,"我是备份",18,new Date());
return user;
}
/**
* 默认降级逻辑处理方法。返回类型必须与调用方法一致,并且不能有参数
*/
private User defaultFallback(){
User user = new User(1L,"我是默认备份",18,new Date());
return user;
}
}
服务熔断
如果一个服务实例,访问的时候经常出现失败,就可以暂时将这个服务实例隔离,就是熔断
熔断原理
熔断器( Circuit Breaker),也叫断路器
断路器本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。
在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
Hystix的熔断状态机模型:
状态机有3个状态:
-
Closed:关闭状态(断路器关闭),所有请求都正常访问。
-
Open:打开状态(断路器打开),所有请求都会被降级。
Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
假设在一定时间内,即时一共19次请求都是失败的,只要没达到20次,不会打开熔断器,如果达到了20次,并且失败比例大于50%,也就是只要失败次数到达10次,就会触发熔断
触发熔断后,断路器在一定的统计时间内(默认是5秒)会一直处于Open状态,这段时间内一切请求都会直接被降级处理
-
Half Open:半开状态, Open状态不是永久的,打开后会进入休眠时间(默认是5S)。结束休眠时间后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
熔断机进入Open状态的条件:
- 请求失败的次数达到阈值,默认是20次
- 请求失败的比例达到阈值,默认是50%
拓展
Sentinel和Hystrix对比
Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。
对比内容 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |