概述
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
比如下图中,一个请求可能需要调用4个模块。加入其中一个模块出现了问题,那么将导致整个模块不可用
当所有的模块均正常的时候
当出现调用链中有一个模块出现异常
如果不作处理,后面的请求一直进来堆积到系统崩溃
雪崩效应:
- 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
- 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
- 所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
- Hystrix是什么
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
- “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
- 功能
- 服务降级
- 服务熔断
- 接近实时的监控
官网资料:GitHub Hystrix
Hystrix已经停止更新
Hystrix重要概念
- 服务降级(Fallback):系统有限资源的合理协调
- 概念:服务降级一般是指在服务器压力剧增的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行。
- 原因:服务器的资源是有限的,而请求是无限的。在用户使用即并发高峰期,会影响整体服务的性能,严重的话会导致宕机,以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性,就需要对某些服务降级处理。可以理解为舍小保大
- 应用场景:多用于微服务架构中,一般当整个微服务架构整体的负载超出了预设的上限阈值(和服务器的配置性能有关系),或者即将到来的流量预计会超过预设的阈值时(比如双11、6.18等活动或者秒杀活动)
- 服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
- 服务熔断(Break):应对雪崩效应的链路自我保护机制,可看做服务降级的特殊情况
- 概念:应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝
- 原因:微服务之间的数据交互是通过远程调用来完成的。服务A调用服务,服务B调用服务C,某一时间链路上对服务C的调用响应时间过长或者服务C不可用,随着时间的增长,对服务C的调用也越来越多,然后服务C崩溃了,但是链路调用还在,对服务B的调用也在持续增多,然后服务B崩溃,随之A也崩溃,导致雪崩效应
- 服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
- 服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
对比服务降级和服务熔断
- 触发原因不一样,服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
- 管理目标层次不一样,服务熔断是一个框架层次的处理,服务降级是业务层次的处理
- 实现方式不一样,服务熔断一般是自我熔断恢复,服务降级相当于人工控制
- 触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
- 服务限流(FlowLimit)
- 在高并发场景下,为保证在现有资源条件下服务正常运行,使用服务限流让请求和并发在应用可接受的范围内,达到高可用的目的。
Hystrix案例
服务端程序
创建Module
创建带Hystrix的服务端
修改pom文件
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>top.chasingwind</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写配置文件
编写主程序类
编写业务类
Service
Controller
测试
调用正常,以此为基础,后续分别从正确—>错误—>降级熔断—>恢复来实现案例
Jmeter压力测试
- 压力测试hystrixtimeout,查看对hystrixok的影响
hystrixok可以立即返回结果
压力测试hyatrixtimeout
可以发现连hystrixok也开始卡顿。因为大多数线程都去执行了hystrixtimeout请求,hystrixok只能分到少数线程来执行。
客户端程序
创建Module
修改pom文
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>top.chasingwind</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写配置文件
主启动类
业务类
Service存放服务端暴露的接口
Controller提供服务
测试
通过客户端调用服务端的hystrixok,可以立即返回结果
对服务端hystrixtimeout进行压测,然后通过客户端调用服务端的hystrixok服务
可以看到客户端调用出现卡顿
由于服务端hystrixtimeout占用了绝大多数线程,导致其他服务只有极少数线程处理,从而对客户端造成卡顿的体验。
因此需要对服务进行降级、熔断或者限流处理
问题的解决
- 超时导致服务器变慢:超时不再等待
- 宕机或者程序错误:出错要有兜底策略
- 客户端调用服务端,当服务端超时或宕机或者程序错误,客户端不能一直等待,必须要有服务降级
- 服务端正常,客户端出现故障或者服务端执行时间超过客户端预期,那么也需要进行服务降级
服务降级
服务端服务降级
配置超时或者出错的FallBack
服务端配置FallBack方法
主启动类添加注解
主启动类添加注解
@EnableCircuitBreaker
测试
超时调用FallBack方法
并且可以注意到他使用的线程不是Web服务器中的线程
对比Web服务器中的线程为
演示程序出错进行FallBack
测试(应该显示报错,未修改FallBack中的语句)
客户端服务降级
配置文件修改
主启动类添加注解
@EnableHystrix
业务类
对接口进行服务降级,当对外提供的接口服务出现错误或者超时,调用FallBack方法
客户端超时服务降级
测试
首先保证服务端正常执行
调用客户端,由于服务端执行需要sleep3s,同时客户端限制服务端1.5s未返回结果,就执行客户端的FallBack方法
演示客户端出现错误进行FallBack
测试
客户端出错也会执行FallBack方法
存在的问题1:每个方法都需要配置一个FallBack方法
没有必要为每一个方法都提供一个FallBack方法,可以使用全局的FallBack方法。
如果有需要定制的FallBack方法,才需要单独进行处理。
测试
存在的问题2:FallBack方法和业务代码混在一起
代码修改
进行解耦:在客户端的Service层进行FallBack处理
创建一个专门处理FallBack的类,实现服务接口。表示对整个服务进行处理
并且在Service接口上进行配置。
表示当调用服务端的方法出现超时或者异常的时候,客户端进行处理的FallBack方法
注意一点:由于默认超时时间为1s,太短了,所以需要在配置文件中进行配置
测试
超时测试
测试:当前客户端和服务端都正常,服务端timeout服务sleep3s。
由于客户端设置的超时时间是6s,所以并未调用FallBack
当服务端sleep7s,但是由于服务端超时时间是10s,其实服务端并未调用FallBack
调用客户端,客户端进行FallBack超时处理
服务不可用
当服务端的服务挂掉之后,客户端直接进行了FallBack处理
服务不可用会自动进行FallBack处理
服务熔断
- 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
- 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,默认是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
参考论文:Martin Fowler’s CircuitBreaker
对请求进行包装,并统计请求的错误率,当达到一定阈值,断路器触发,此时所有再次进来的请求将直接返回错误
断路器中的状态
案例演示
服务端修改
客户端添加接口
测试
正确的请求和错误的请求
当一直调用错误的请求时,达到触发断路器的阈值之后,再次调用正确的请求
发现正确的请求也调用了Fallback方法
等待一段时间后,再次调用正确的请求,又恢复正常了
以上过程充分体现了:服务降级—>进而熔断—>逐渐恢复调用链路
以上服务端中的方法熔断配置对应的配置类为
HystrixCommandProperties
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阈值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该Hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阈值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
- 服务熔断总结
- 断路器打开条件:在快照时间窗口内,请求总数达到阈值并且错误百分比也达到阈值
- 断路器打开之后
- 再有请求调用时,将不会调用主逻辑,而是直接调用降级Fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
- 恢复调用链路
- 对于这一问题,Hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,Hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上**,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
- 对于这一问题,Hystrix也为我们实现了自动恢复功能。
Hystrix工作流程
- Hystrix的Github图示工作流程
Hystrix Dashboard
- 除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
创建监控模块
- 创建Module
修改pom文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写配置文件
编写主启动类
启动并测试
监控服务
- 首先,被监控的服务需要具有依赖
spring-boot-starter-actuator
- 需要添加配置Bean到容器中
- 首先,被监控的服务需要具有依赖
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
- 启动服务并查看监控页面
当一直调用正确的请求
一直调用错误的请求
然后调用一次正确的请求,断路器从Open—>Close