SpringCloud之Hystrix【服务降级、服务熔断、服务限流】

2022-08-01,,,,

Hystrix

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布是系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的的线程不会被长时间、不必要的占用,从而避免对了故障在分布式系统中分蔓延、雪崩

1.服务降级

比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案
哪些情况可能出现降级:

  1. 程序运行异常
  2. 超时
  3. 服务熔断歘服务降级
  4. 线程池、信号量打满导致服务降级

先模拟场景

  1. 建服务提供者模块cloud-provider-hystrix-payment8001
  2. 导依赖
  3. 编写配置文件
server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true  # true向服务中心注册自己
    fetch-registry: true  # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka  # 注册地址和端口
  instance:
    instance-id: payment-hystrix-8001
    prefer-ip-address: true  # 访问路径可以显示IP地址
  1. 编写业务代码
    创建两个方法,一个正常返回【paymentInfo_OK】,另一个休眠5秒【paymentInfo_TimeOut】
  2. 编写启动类,加上@EnableEurekaClient注解,注册进Eureka注册中心
  3. 启动测试、压力测试,进行自测
    发现低并发量时,方法的访问都正常。但是压测时,如果大流量请求休眠的方法时,访问正常方法的响应速度也会受影响,导致卡顿变慢

此时再建一个客户端模块cloud-consumer-feign-hystrix-order80,模拟在服务提供者自身承载着比较大的压力的情况下,此时消费者也来调用服务的场景

  1. 建模块
  2. 导依赖
    Hystrix、OpenFeign
  3. 写配置
server:
  port: 80

eureka:
  client:
    register-with-eureka: false  # true向服务中心注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka  # 注册地址和端口
#记得配置这两个属性,否则会发生超时异常
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000
  1. 写主启动类,并添加@EnableFeignClients注解
  2. 添加service接口
@Component
@FeignClient(value = "cloud-provider-hystrix-payment")
public interface PaymentHystrixService {
    @GetMapping("payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);
    @GetMapping("payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
  1. 写controller调用
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;
    @GetMapping("consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    @GetMapping("consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
}
  1. 启动压力测试,客户端80去访问压力较大的服务提供方8001
    出现80客户端调用响应缓慢,甚至超时的情况

解决

  1. 超时导致服务器变慢(转圈) ———— 超时不再等待
  2. 出错(岩机或程序运行出错) ———— 出错要有兜底

对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)没问题,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者,自己处理降级)

服务提供端处理

  1. 在目标方法上加注解@HystrixCommand,启用服务降级,并添加降级方法paymentInfo_TimeOut_Handler
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOut_Handler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })

fallbackMethod表示降级的方法的方法名(自定义),commandProperties后面部分表示,设置自身调用超时时间的峰值为3秒,峰值内可以正常运行,超过了需要有兜底的方法处理,做服务降级fallback
2. 在主启动类上添加@EnableCircuitBreaker注解,激活服务降级
3. 8001自测,是否因为休眠导致超时或者发生异常,就会服务降级,执行fallback方法。

服务消费端处理

  1. 增加yml配置项
feign:
  hystrix:
    enabled: true
  1. 主启动类上加@EnableHystrix注解
  2. 然后到方法的实现上加@HystrixCommand注解,并定义降级方法paymentTimeOutFallbackMethod
@GetMapping("consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
           @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
  1. 启动测试,分别测试超时和运行异常,是否都会执行服务降级方法

完善

上述的做法存在的问题:

  1. 一个方法就得对应一个降级方法,不可取,导致代码膨胀
  2. 降级方法的定义与业务逻辑代码糅杂在一起,耦合度高
  • 《解决问题1》在方法实现的类上引入@DefaultProperties(defaultFallback="方法名"),这样,除了个别专门配置了降级方法的,其他只有@HystrixCommand这一个注解(注解后没其他属性)标注的方法,都可以通过defaultFallback指定的方法来做统一的处理(服务提供端与服务消费方操作一样)
  • 《解决问题2》只需在Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。添加接口的一个实现类,实现对降级方法的集中定义,要保证yml文件中feign.hystrix.enabled=true,然后在接口@FeignClient的注解中添加一个fallback属性的配置@FeignClient(value = “cloud-provider-hystrix-payment”, ·fallback =PaymentFallbackService.class)。
    注意:采用这种方式,就不需要在具体的目标方法上添加@HystrixCommand注解了,如果加了,@HystrixCommand的优先级更高一些。

2.服务熔断

当某个服务出现问题卡死了,不能让用户一直等待,需要关闭所有对此服务的访问,然后调用服务降级

  1. 在服务提供端的Service中,定义一个测试断路器的方法和降级方法
// 服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数峰值
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期,失败后等待多久再进行重试,单位为毫秒
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
    if(id < 0){
        throw new RuntimeException("=============id不能为负数============");
    }
    String serialNumber = IdUtil.simpleUUID();
    return Thread.currentThread().getName()+"\t"+"调用成功,流水号为"+serialNumber;
}

public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id不能为负数,请稍后再试,id为"+id;
}

属性配置在这个类中可以查看
com.netflix.hystrix.HystrixCommandProperties
2. 在controller中调用
3. 测试:大量调用会产生异常的方法,马上请求调用正常的方法,会发现熔断器打开,没法调用正常的方法,但会慢慢恢复

总结

熔断开启的条件

  • 当满足一定的阈值时,默认10秒内超过20个请求
  • 当失败率达到一定值,默认10秒内超过50%的请求失败

一段时间后(默认5秒),这个时候熔断器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,如果失败,继续开启,重新计时。


熔断整体流程:

  • 请求进来,首先查询缓存,如果缓存有,直接返回,如果缓存没有,进入下面步骤
  • 查看断路器是否开启
    • 如果开启的,Hystrix直接将请求转发到降级返回,然后返回
    • 如果断路器是关闭的,判断线程池等资源是否已经满了
      • 如果已经满了,也会走降级方法
      • 如果资源没有满,判断我们使用的什么类型的Hystrix,决定调用构造方法还是run方法,然后处理请求,Hystrix会将本次请求的结果信息汇报给断路器,因为断路器此时可能是开启的(断路器开启也是可以接收请求的),断路器收到信息,判断是否符合开启或关闭断路器的条件
        • 如果本次请求处理失败,又会进入降级方法
        • 如果处理成功,判断处理是否超时
          • 如果超时了,也进入降级方法
          • 没有超时,则本次请求处理成功,将结果返回给controller

断路器逻辑

  • 当断路器开启,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback,通过断路器,实现了自动地发现错误,并将降级逻辑切换为主逻辑,减少响应延迟的效果。
  • 原来的主逻辑要如何恢复呢?
    对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,断路器关闭,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

3.服务限流

限流,比如秒杀场景,不能让用户瞬间都访问服务器,限制一次只可以有多少请求

后面学习sentinal时再总结

Hystrix Dashboard监控

  1. 建模块
  2. 导依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
  1. 写配置
server:
	port: 9001
  1. 新建主启动类,添加@EnableHystrixDashboard注解
  2. 启动,服务http://localhost:9001/hystrix
    此时要监控服务,还需要在在服务的主启动类中添加这么一个Bean的配置,才能监控到服务
    (cloud-provider-hystrix-payment8001为例)
/**
    * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
    * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
    * 只要在自己的项目里配置上下面的servlet就可以了
    * I
    */
   @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;
   }

填入监控服务的IP+端口/hystrix.stream

cloud-provider-hystrix-payment8001的访问都可以在Dashboard上监控到

END

本文地址:https://blog.csdn.net/K_kzj_K/article/details/107360512

《SpringCloud之Hystrix【服务降级、服务熔断、服务限流】.doc》

下载本文的Word格式文档,以方便收藏与打印。