微服务中的雪崩效应

什么是微服务中的雪崩效应

在微服务系统中,一个请求可能需要多个微服务接口之间层级调用才能完成一套业务流程的处理,此时就会形成复杂的调用链路。类似如图所示:
image.png
此时,如果 自动投递微服务 调用的 下层服务 简历微服务 在处理请求的时候出现了严重超时的情况,就会导致 自动投递微服务 会一直持续等待响应结果,进而也导致了超时的情况,然后也会继续影响上层服务 用户微服务 等待 自动投递微服务 的响应结果,最终也导致了 用户微服务 也出现了超时的情况。超时的问题,导致请求所对应的线程一直得不到释放,最后就会导致太多的线程一直无法释放,极大占用了CPU资源,进而导致从下层服务至上层服务等所有服务都崩溃了。 这就是微服务的雪崩效应。
image.pngimage.pngimage.png

扇入和扇出

扇入: 代表该微服务被调用的次数。 扇入大,说明该微服务模块复用性好。
扇出: 代表该微服务调用其他微服务的个数。扇出大,说明该微服务模块业务逻辑负载。

在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。 这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的扇出。如果扇出的链路上某个微服务的调用相应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源(大量请求阻塞,大量线程不会释放,服务器资源耗尽),进而引起系统崩溃,产生所谓的“雪崩效应”。

雪崩效应解决方案

从可用性和可靠性着想,为了防止系统的整体缓慢甚至崩溃,可以采用以下三种技术应对微服务中的雪崩效应。

服务熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制。
熔断:类似生活场景中的,高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。
在微服务架构中,熔断机制也起着类似的作用。当扇出链路的某个微服务不可用或者响应时间太长时,就会熔断该节点对微服务的调用,进行服务的降级,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断重点在“”,切断对下游服务的调用;
服务熔断和服务降级往往一起使用。

服务降级

通俗讲就是系统整体资源不够用,会将一些不紧要的服务停掉(调用服务的时候,会返回一个兜底数据),待度过难关高峰后,再把这些服务重新打开。
服务降级一般是从整体性考虑,就是当某个服务熔断之后,服务器将不再被调用,此刻客户端可以自己准备一个本地的fallback回调,返回一个缺省值。

服务限流

服务降级是当服务出现问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰过去或者问题解决后在打开;但是有些场景并不能用服务降级来解决,比如秒杀业务这样的核心功能,这种情况就需要结合服务限流来限制这些场景的并发请求量。
限流措施:

  • 限制总并发数(比如数据库连接池,线程池)
  • 限制瞬时并发数(比如nginx限制瞬时并发连接数)
  • 限制时间窗口的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒平均速率)
  • 限制远程接口调用速率,限制MQ的消费速率等

    Hystrix简介

    Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三库,防止级联失败,从而提升系统的可用性与容错性。
    Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用 HystrixCommand 包裹对依赖的调用逻辑。服务消费者方法( @HystrixCommand 添加Hystrix控制)— 调用服务提供者方法。

  • 跳闸机制:当某个服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱避模式)。如果该线程池已满,发往该依赖的请求就会被立即拒绝,而不是排队等待,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或者当断路器打开时,就会执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。

    Hystrix熔断应用

    目的

    在简历微服务(服务提供者)长时间没有响应,自动投递微服务(服务消费者)快速失败给用户提示。image.png

    服务消费者工程

    引入Hystrix依赖坐标

    1. <!--熔断器Hystrix-->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-netflixhystrix</artifactId>
    5. </dependency>

    启动类添加熔断器开启注解

    注解:@EnableCircuitBreaker
    1. @SpringBootApplication
    2. @EnableDiscoveryClient // 开启服务发现
    3. @EnableCircuitBreaker // 开启熔断
    4. public class AutodeliverApplication {
    5. public static void main(String[] args) {
    6. SpringApplication.run(AutodeliverApplication.class, args);
    7. }

    定义服务降级处理方法

    定义服务降级处理方法,并在业务方法上使用 @HystrixCommandfallbackMethod 属性关联到服务降级处理方法。 ```java /**
    • 提供者模拟处理超时,调⽤⽅法添加Hystrix控制
    • @param userId
    • @return */ // 使⽤@HystrixCommand注解进⾏熔断控制 @HystrixCommand( // 线程池标识,要保持唯⼀,不唯⼀的话就共⽤了 threadPoolKey = “findResumeOpenStateTimeout”, // 线程池细节属性配置 threadPoolProperties = {
      1. @HystrixProperty(name="coreSize",value ="1"), // 线程数
      2. @HystrixProperty(name="maxQueueSize",value="20") // 等待队列⻓度
      }, // commandProperties熔断的⼀些细节属性配置 commandProperties = {
      1. // 每⼀个属性都是⼀个HystrixProperty
      2. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")
      } ) @GetMapping(“/checkStateTimeout/{userId}”) public Integer findResumeOpenStateTimeout(@PathVariable Long userId) { String url = “http://lagou-serviceresume/resume/openstate/“ + userId; // 指定服务名 Integer forObject = restTemplate.getForObject(url, Integer.class); return forObject; }

@GetMapping(“/checkStateTimeoutFallback/{userId}”) @HystrixCommand( // 线程池标识,要保持唯⼀,不唯⼀的话就共⽤了 threadPoolKey = “findResumeOpenStateTimeoutFallback”, // 线程池细节属性配置 threadPoolProperties = { @HystrixProperty(name=”coreSize”,value = “2”), // 线程数 @HystrixProperty(name=”maxQueueSize”,value=”20”) // 等待队列⻓度 }, // commandProperties熔断的⼀些细节属性配置 commandProperties = { // 每⼀个属性都是⼀个HystrixProperty @HystrixProperty(name=”execution.isolation.thread.timeoutInMilliseconds”,value=”2000”) // hystrix⾼级配置,定制⼯作过程细节 // 统计时间窗⼝定义 @HystrixProperty(name = “metrics.rollingStats.timeInMilliseconds”,value = “8000”), // 统计时间窗⼝内的最⼩请求数 @HystrixProperty(name = “circuitBreaker.requestVolumeThreshold”,value = “2”), // 统计时间窗⼝内的错误数量百分⽐阈值 @HystrixProperty(name = “circuitBreaker.errorThresholdPercentage”,value = “50”), // ⾃我修复时的活动窗⼝⻓度 @HystrixProperty(name = “circuitBreaker.sleepWindowInMilliseconds”,value = “3000”) }, fallbackMethod = “myFallBack” // 回退⽅法 ) public Integer findResumeOpenStateTimeoutFallback(@PathVariable Long userId) { String url = “http://lagou-serviceresume/resume/openstate/“ + userId; // 指定服务名 Integer forObject = restTemplate.getForObject(url, Integer.class); return forObject; }

/*

  • 定义回退⽅法,返回预设默认值
  • 注意:该⽅法形参和返回值与原始⽅法保持⼀致 **/ public Integer myFallBack(Long userId) { return -123333; // 兜底数据 } ```

    注意:降级(兜底)方法必须和被降级方法相同的方法签名(相同的参数列表,相同的返回值);

可以在类上使用 @DefaultProperties 注解统一指定整个类中共用的降级(兜底)方法。

服务提供者工程

服务提供者模拟请求超时

  1. @GetMapping("/openstate/{userId}")
  2. public Integer findResumeOpenState(@PathVariable Long userId) {
  3. // 模拟请求超时,触发服务消费者端熔断降级
  4. try {
  5. Thread.sleep(3000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. return port;
  10. }

Hystrix舱壁模式(线程池隔离策略)

原始Hystrix存在问题

Hystrix默认有一个线程池(线程数量为10个),供所有的添加了 @HystrixCommand 的方法提供线程,如果这些方法接收请求超过了10个,其他请求就会等待或者拒绝连接。(这种情况并不是因为扇出链路微服务不可用导致的,而是因为线程机制导致。如果方法A的请求把10个线程都用了,方法2请求处理的时候就因为没有线程可用导致无法访问B)。
image.png

Hystrix舱壁模式

为了避免因为服务请求过多,处理的线程不足导致正常服务无法访问,Hystrix为每个单独的控制方法创建一个线程池的方式(舱壁模式)来解决该问题。
image.png

使用相关命令查看线程情况 jps命令:查看系统运行的java进程。 jstack命令:查看指定进程的线程信息。 jstack (pid) | grep hystrix : 查看指定进程(pid)下的线程信息,并过滤出和hystrix有关的线程信息。

代码示例

  1. /**
  2. * 提供者模拟处理超时,调⽤⽅法添加Hystrix控制
  3. * @param userId
  4. * @return
  5. */
  6. // 使⽤@HystrixCommand注解进⾏熔断控制
  7. @HystrixCommand(
  8. // 线程池标识,要保持唯⼀,不唯⼀的话就共⽤了
  9. threadPoolKey = "findResumeOpenStateTimeout",
  10. // 线程池细节属性配置
  11. threadPoolProperties = {
  12. @HystrixProperty(name="coreSize",value = "1"), // 线程数
  13. @HystrixProperty(name="maxQueueSize",value="20") // 等待队列⻓度
  14. },
  15. // commandProperties熔断的⼀些细节属性配置
  16. commandProperties = {
  17. // 每⼀个属性都是⼀个HystrixProperty
  18. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")
  19. }
  20. )
  21. @GetMapping("/checkStateTimeout/{userId}")
  22. public Integer findResumeOpenStateTimeout(@PathVariable Long userId) {
  23. String url = "http://lagou-serviceresume/resume/openstate/" + userId; // 指定服务名
  24. Integer forObject = restTemplate.getForObject(url, Integer.class);
  25. return forObject;
  26. }
  27. @GetMapping("/checkStateTimeoutFallback/{userId}")
  28. @HystrixCommand(
  29. // 线程池标识,要保持唯⼀,不唯⼀的话就共⽤了
  30. threadPoolKey = "findResumeOpenStateTimeoutFallback",
  31. // 线程池细节属性配置
  32. threadPoolProperties = {
  33. @HystrixProperty(name="coreSize",value = "2"),// 线程数
  34. @HystrixProperty(name="maxQueueSize",value="20") // 等待队列⻓度
  35. },
  36. // commandProperties熔断的⼀些细节属性配置
  37. commandProperties = {
  38. // 每⼀个属性都是⼀个HystrixProperty
  39. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")
  40. },
  41. fallbackMethod = "myFallBack" // 回退⽅法
  42. )
  43. public Integer findResumeOpenStateTimeoutFallback(@PathVariable Long userId) {
  44. String url = "http://lagou-serviceresume/resume/openstate/" + userId; // 指定服务名
  45. Integer forObject = restTemplate.getForObject(url, Integer.class);
  46. return forObject;
  47. }

Hystrix工作流程与高级应用

Hystrix工作流程

image.png
工作流程:

  1. 当请求调用出现问题时,会开启一个时间窗(默认为10s);
  2. 在这个时间窗内,会统计调用次数是否达到了最小请求数:
    1. 如果没有达到,则重置统计信息,回到第1步;
    2. 如果达到了,则统计失败请求数占所有请求数的百分比,是否达到了阈值?
      1. 如果达到,则跳闸,不再请求对应的服务;
      2. 如果没有达到,则重置统计信息,回到第1步;
  3. 如果跳闸了,会开启一个活动窗口(默认5s),每隔5sHystrix会让一个请求通过,到达出现问题的服务,看是否能够调用成功,如果成功,则重置断路器回到第1步,如果失败,回到第3步。

自定义断路器行为

注解方式

  1. /**
  2. * 8秒钟内,请求次数达到2个,并且失败率在50%以上,就跳闸
  3. * 跳闸后活动窗⼝设置为3s
  4. */
  5. @HystrixCommand(
  6. commandProperties = {
  7. // 请求时间窗时间
  8. @HystrixProperty(name ="metrics.rollingStats.timeInMilliseconds",value = "8000"),
  9. // 请求最小次数
  10. @HystrixProperty(name ="circuitBreaker.requestVolumeThreshold",value = "2"),
  11. // 错误请求数占总请求数阈值
  12. @HystrixProperty(name ="circuitBreaker.errorThresholdPercentage",value = "50"),
  13. // 跳闸后的活动窗口时间
  14. @HystrixProperty(name ="circuitBreaker.sleepWindowInMilliseconds",value = "3000")
  15. }
  16. )

配置文件方式

  1. # 配置熔断策略:
  2. hystrix:
  3. command:
  4. default:
  5. circuitBreaker:
  6. # 强制打开熔断器,如果该属性设置为true,强制断路器进⼊打开状态,将会拒绝所有的请求。
  7. # 默认false关闭的
  8. forceOpen: false
  9. # 触发熔断错误⽐例阈值,默认值50%
  10. errorThresholdPercentage: 50
  11. # 熔断后休眠时⻓,默认值5秒
  12. sleepWindowInMilliseconds: 3000
  13. # 熔断触发最⼩请求次数,默认值是20
  14. requestVolumeThreshold: 2
  15. execution:
  16. isolation:
  17. thread:
  18. # 熔断超时设置,默认为1秒
  19. timeoutInMilliseconds: 2000

健康检查观察跳闸状态

访问健康检查接口: http://localhost:port/actuator/health

  1. # springboot中暴露健康检查等断点接⼝
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: "*"
  7. # 暴露健康接⼝的细节
  8. endpoint:
  9. health:
  10. show-details: always

Hystrix正常状态

image.png

Hystirx跳闸状态

image.png

活动窗口内自我修复

image.png

Hystrix Dashboard短路监控仪表盘

正常状态为UP,跳闸状态为CIRCUIT_OPEN。
可以在工程引入SpringBoot的actuator(健康监控),通过/actuator/heath查看。它提供了很多监控所需的接口,可以对应用系统进行配置查看、相关功能统计等。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

如果我们想看到Hystrix相关数据,比如有多少请求、多少成功、多少失败、多少降级等。那么引入Spring Boot健康监控后,访问 /actuator/hystrix.stream 接口可以获取到监控的文字信息,但是不直观,所以Hystrix提供了基于图形化的Dashboard(仪表盘)监控平台。

Hystrix Dashboard

Hystrix Dashboard可以显示每个断路器(被@HystrixCommand注解的方法)的状态。
image.png
自动投递微服务,做一些配置,通过一个接口就可以获取到监控数据 /actoator/hystrix.stream ;
仪表盘项目需要单独的创建Dashboard功能,提供直观的页面方式展示Hystrix执行的数据;

服务搭建

(1) 新建一个监控服务工程,导入依赖

  1. <!--hystrix-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  5. </dependency>
  6. <!--hystrix 仪表盘-->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-netflix-hystrixdashboard</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.cloud</groupId>
  13. <artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
  14. </dependency>

(2) 启动类添加@EnableHystrixDashboard激活仪表盘

  1. @SpringBootApplication
  2. @EnableHystrixDashboard // 开启hystrix dashboard
  3. public class HystrixDashboardApplication9000 {
  4. public static void main(String[] args) {
  5. SpringApplication.run(HystrixDashboardApplication.class, args);
  6. }
  7. }

(3) application.yml配置

  1. server:
  2. port: 9000
  3. spring:
  4. application:
  5. name: lagou-cloud-hystrix-dashboard
  6. eureka:
  7. client:
  8. serviceUrl: # eureka server的路径
  9. #把eureka集群中的所有url都填写了进来,也可以只写⼀台,因为各个eureka server可以同步注册表
  10. defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/
  11. instance:
  12. #使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
  13. prefer-ip-address: true
  14. #⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
  15. instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@

(4) 在被监测的微服务中注册监控servlet

  1. @Bean
  2. public ServletRegistrationBean getServlet(){
  3. HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
  4. ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
  5. registrationBean.setLoadOnStartup(1);
  6. registrationBean.addUrlMappings("/actuator/hystrix.stream");
  7. registrationBean.setName("HystrixMetricsStreamServlet");
  8. return registrationBean;
  9. }

(5) 访问测试 http://ip:port/htstrix,输入监控的微服务端点地址,展示监控的详细数据。
image.png
image.png
百分比:10s内错误请求百分比
实心圆:1. 大小:代表请求流量的大小,流量越大球越大;2. 颜色:代表请求处理的健康状态,从绿色到红色递减,绿色代表健康,红色代表不健康。
曲线波动图:记录2分钟内该方法上的流量变动波动图,判断流量上升或者下降的趋势。

Hystrix Turbine聚合监控

Hystrix Turbine

Hystrix Dashboard针对的是每一个微服务实例的监控。但是在微服务架构中,一个微服务往往会有多个实例(集群化)。所以针对每个微服务的实例进行监控的话,需要每次都在Dashboard仪表盘里输入一个监控数据url进行查看,操作方面上难免会比较繁琐。
可以使用 Hystrix Turbine进行聚合( 聚合各个实例的Hystrix监控数据)监控。
image.png

服务搭建

(1)新建项目 cloud-hystrix-turbine-9001 ,引入依赖坐标

  1. <dependencies>
  2. <!--hystrix turbine聚合监控-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-netflixturbine</artifactId>
  6. </dependency>
  7. <!--
  8. 引⼊eureka客户端的两个原因
  9. 1、微服务架构下的服务都尽量注册到服务中⼼去,便于统⼀管理。
  10. 2、后续在当前turbine项⽬中我们需要配置turbine聚合的服务,⽐如,
  11. 我们希望聚合service-autodeliver这个服务的各个实例的hystrix数据流,
  12. 那随后我们就需要在application.yml⽂件中配置这个服务名,那么turbine
  13. 获取服务下具体实例的数据流的时候需要ip和端⼝等实例信息,
  14. 那么怎么根据服务名称获取到这些信息呢?当然可以从eureka服务注册中⼼获取。
  15. -->
  16. <dependency>
  17. <groupId>org.springframework.cloud</groupId>
  18. <artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
  19. </dependency>
  20. </dependencies>

(2)将需要进行Hystrix监控的多个微服务配置起来,在工程application.yml中开启Turbine及进行相关配置

  1. server:
  2. port: 9001
  3. spring:
  4. application:
  5. name: lagou-cloud-hystrix-turbine
  6. eureka:
  7. client:
  8. serviceUrl: # eureka server的路径
  9. #把 eureka 集群中的所有url都填写了进来,也可以只写⼀台,因为各个eureka server可以同步注册表
  10. defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/
  11. instance:
  12. #使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
  13. prefer-ip-address: true
  14. #⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
  15. instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
  16. #turbine配置
  17. turbine:
  18. # appCofing配置需要聚合的服务名称,⽐如这⾥聚合⾃动投递微服务的hystrix监控数据
  19. # 如果要聚合多个微服务的监控数据,那么可以使⽤英⽂逗号拼接,⽐如 a,b,c
  20. appConfig: lagou-service-autodeliver
  21. clusterNameExpression: "'default'" # 集群默认名称

(3) 启动类项目添加注解 @EnableTurbine , 开启仪表盘以及Turbine聚合

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. @EnableTurbine // 开启聚合功能
  4. public class HystrixTurbineApplication9001 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(HystrixTurbineApplication9001.class, args);
  7. }
  8. }

(4) 浏览器访问Turbine项目,http://ip:port/turbine.stream,就可以看到监控数据。
通过 http://ip:port/hystrix(dashboard页面)在输入框输入 http://ip:port/turbine.stream,就可以使用在监控页面直观地展示监控数据。
image.png