欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

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

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

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秒),这个时候熔断器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,如果失败,继续开启,重新计时。


熔断整体流程:
SpringCloud之Hystrix【服务降级、服务熔断、服务限流】

  • 请求进来,首先查询缓存,如果缓存有,直接返回,如果缓存没有,进入下面步骤
  • 查看断路器是否开启
    • 如果开启的,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
SpringCloud之Hystrix【服务降级、服务熔断、服务限流】
cloud-provider-hystrix-payment8001的访问都可以在Dashboard上监控到
SpringCloud之Hystrix【服务降级、服务熔断、服务限流】
END

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

相关标签: SpringCloud