程序员L札记

V1

2022/05/05阅读:20主题:橙心

Spring Cloud Hystrix实战(一)


概述

在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。

服务降级

当出现以下情况,服务器忙,不让客户端等待并立刻返回一个友好提示,fallback

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满

注意:服务降级并不代表断路器打开

服务熔断

当服务器请求超过最大数,直接拒绝访问,然后调用服务降级方法并返回一个友好提示

服务限流

秒杀高并发等操作,严禁拥挤,排队有序进行

基础用法

  1. Hystrix依赖
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 </dependency>
  1. 添加@EnableHystrix或者是@EnableCircuitBreaker

Hystrix与RestTemplate

服务降级:当消费端发送请求到客户端,客户端出现超时、异常、宕机,此时需要有兜底处理方式,不至于整个服务崩溃。

  • 服务出现问题,则将请求转到默认处理返回方法(友好提示)
  • 客户端和消费端都可以做,一般放在消费端
@RestController
@RequestMapping("/user")
public class UserCenterController {
    private final RestTemplate restTemplate;

    public UserCenterController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/{id}")
    @HystrixCommand(
            // 服务降级方法
            fallbackMethod = "orderFallBack",
            // 使用commandProperties 可以配置熔断的一些细节信息
            commandProperties = {
                    // 类似kv形式
                    // 这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行服务降级
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
            }
    )
    public Integer getTodayOrders(@PathVariable("id") Integer id) {
        String url = "http://spring-cloud-order-service-provider/order/getOrder/" + id;
        return restTemplate.getForObject(url, Integer.class);
    }

    // 服务降级方法 ,这里参数与返回值需要原方法保持一直
    public Integer orderFallBack(Integer id) {
        return -1;
    }
}

这里对getTodayOrders方法设置了服务降级的方法orderFallBack,当执行时间超过2秒,就会进行服务降级,从而调用orderFallBack方法。

Hystrix与OpenFeign

application.yml中开启feign对hystrix的支持

feign:
  hystrix:
    enable: true

有两种不同的方式在消费端配置

  • 在controller层
  • 利用@FeignClient注解的fallback属性

同样支持RestTemplate方式

Controller业务层

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService PaymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    @HystrixCommand
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        int i = 10 / 0;
        return PaymentHystrixService.paymentInfo_OK(id);
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            //超过2秒及服务降级
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        return PaymentHystrixService.paymentInfo_TimeOut(id);
    }

    //此方法为paymentInfo_TimeOut方法的专属降级方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "对方支付系统繁忙,请稍后再试  ==>paymentInfo_TimeOut方法专属降级方法";
    }

    //全局公共的降级方法
    public String paymentGlobalFallbackMethod() {
        return "对方支付系统繁忙,请稍后再试  ==>全局公共降级方法";
    }
}

结论和注意点:

  • @DefaultProperties注解为全局降级方法,对于添加@HystrixCommand注解的方法会走全局公共降级方法
  • @HystrixCommand(fallbackMethod = “xxx” )则为当前方法设置专属的降级方法
  • 注意事项1:设置专属降级方法时候,其降级方法入参必须与注解方法一致,否则报错找不到指定参数的降级方法
  • 注意事项2:如果代码中有异常,比如10/0,不会抛错,只会走降级方法

利用@FeignClient的fallback属性

定义controller

@RestController
@Slf4j
//@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService PaymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        int i = 10/0;
        return PaymentHystrixService.paymentInfo_OK(id);
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        return PaymentHystrixService.paymentInfo_TimeOut(id);
    }
}

定义feign client接口,并通过fallback属性指定服务降级的方法

@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService 
{
    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

定义服务降级方法,实现feign client 接口

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "PaymentFallbackService 的 paymentInfo_OK的降级方法";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "PaymentFallbackService 的 paymentInfo_TimeOut的降级方法";
    }
}

两种方式的区别

  • 在Controller层添加降级方法相当于为消费端和服务端所有代码添加降级方法,无论哪里出现错误,都会进入降级方法
  • 利用feign设置fallback降级方法,相当于为服务端添加降级方法,只有服务端出现异常才会进入降级方法,消费端的异常会将错误抛出,并不会进入降级方法

舱壁模式

什么是舱壁模式

首先舱壁模式就是保证微服务健壮性的一种模式,舱壁模式通过隔离每个工作负载或服务的关键资源,如连接池、内存和CPU来防止由一个服务引起的级联故障来增加系统的弹性。例如我们的船舱,我们船就一个船舱的话,如果破了一个洞,然后海水就慢慢灌满了整艘船,到最后船就沉下去了,但是如果我们将整艘船隔成若干个船舱,像下图这样,如果船破了一个洞,海水顶多会灌满一个船舱,不会导致整艘船沉下去。

enter image description here
enter image description here

舱壁模式在Hystrix的应用

在Hystrix中,使用了线程池隔离策略,Hystrix中有一个线程池(默认是10个线程),然后供所有添加了@HystrixCommand注解的方法使用,如果那些方法的请求超过10个,其他请求就得等待或者拒绝,像下图这样。

enter image description here
enter image description here

里如果不进行设置的话,默认会共用一个线程池,这样很容易出问题,比如说我请求量很大,然后方法A 把这10个线程全用了,而且A后面调用的服务方法处理很慢,然后我方法B跟方法C就没有线程用了,然后就得等待,然后超时被熔断,这里并不是我们后面的服务不可用,不是服务1的某个方法,服务2的某个方法不可用,而是服务调用者压根没有线程去处理这些请求。 然后为了避免这种情况的发生,Hystrix没有通过增加线程数来处理这个问题,而是对每一个添加@HystrixCommand 注解的方法创建线程池来进行隔离,就算是某个方法出了问题也不会影响到其他方法,这就是舱壁模式在Hystrix的应用

enter image description here
enter image description here

这样方法的调用就隔离开了,两个互不影响。

Hystrix线程池隔离配置

我可以使用@HystrixCommand 注解里面的参数threadPoolKey 来定义使用的线程池key,这个需要唯一,不唯一的话共用。然后使用threadPoolProperties属性来配置线程池的一些细节参数 我们修改下服务调用者也就是用户服务的controller

@RestController
@RequestMapping("/user/data")
public class UserCenterController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getTodayStatistic/{id}")
    @HystrixCommand(
            // 线程池标识
            threadPoolKey = "getTodayStatistic",
            threadPoolProperties = {
                    //这个就是咱们那个线程池core线程核心数
                    @HystrixProperty(name = "coreSize", value = "3"),
                    //这个是队列大小
                    @HystrixProperty(name = "maxQueueSize", value = "100")
            },
            // 服务降级方法
            fallbackMethod = "getTodayStatisticFallback",
            // 使用commandProperties 可以配置熔断的一些细节信息
            commandProperties = {
                    //类似kv形式
                    //这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行服务降级
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
            }
    )
    public Integer getTodayStatistic(@PathVariable("id") Integer id) {
        String url = "http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/" + id;
        return restTemplate.getForObject(url, Integer.class);
    }

    // 服务降级方法 ,这里参数与返回值需要原方法保持一直
    public Integer getTodayStatisticFallback(Integer id) {
        return -1;
    }

    @GetMapping("/getTodayStatisticA/{id}")
    @HystrixCommand(
            // 线程池标识
            threadPoolKey = "getTodayStatisticA",
            threadPoolProperties = {
                    //这个就是咱们那个线程池core线程核心数
                    @HystrixProperty(name = "coreSize", value = "2"),
                    //这个是队列大小
                    @HystrixProperty(name = "maxQueueSize", value = "100")
            },
            // 服务降级方法
            fallbackMethod = "getTodayStatisticFallbackA",
            // 使用commandProperties 可以配置熔断的一些细节信息
            commandProperties = {
                    //类似kv形式
                    //这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行服务降级
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
            }
    )
    public Integer getTodayStatisticA(@PathVariable("id") Integer id) {
        String url = "http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/" + id;
        return restTemplate.getForObject(url, Integer.class);
    }

    // 服务降级方法 ,这里参数与返回值需要原方法保持一直
    public Integer getTodayStatisticFallbackA(Integer id) {
        return -1;
    }
}

服务熔断

Hystrix断路器工作流程

当我们调用出现问题的时候,Hystrix会开启一个默认10s的时间窗口,然后在这个窗口时间内,会统计调用次数是否达到了最小请求数,如果没有达到就会重制统计信息, 如果达到了,就会计算统计失败占所有请求的百分比,判断是否到达阈值,如果达到,就会跳闸,不再请求对应服务, 如果失败占所有请求的百分比未达到阈值,然后重置统计信息。 如果跳闸,则会开启一个活动窗口,默认是5s,每隔5s 会让一个请求通过,到达那个有问题的服务,看看是否还有问题,如果没问题就重置断路器,如果有问题,继续每5s通过一个请求来验证。

enter image description here
enter image description here

整个流程,有一些参数我们是可以根据业务来改动:

  • 当出现错误的时候,会开启一个默认10s的窗口,这个10s我们是可以配置的
  • 再就是这个最小请求数
  • 再就是错误请求占比阈值
  • 发生跳闸,然后每隔默认5s来放一个请求探测对方服务,其中这个5s的活动窗口我们是可以配置的。

Hystrix配置

配置方式一:注解方式

@RestController
@RequestMapping("/user/data")
public class UserCenterController {
    private final RestTemplate restTemplate;

    public UserCenterController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/getTodayStatisticB/{id}")
    @HystrixCommand(
            fallbackMethod = "getTodayStatisticFallbackB",// 服务降级方法
            // 使用commandProperties 可以配置熔断的一些细节信息
            commandProperties = {
                    // 类似kv形式
                    // 这里这个参数意思是熔断超时时间2s,表示过了多长时间还没结束就进行服务降级
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
                    // 当遇到失败后,开启一个11s的窗口
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "11000"),
                    // 最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "3"),
                    // 失败次数占请求的50%
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                    // 跳闸后 活动窗口配置 这里配置了10s
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
            }

    )
    public Integer getTodayStatisticB(@PathVariable("id") Integer id) {
        String url = "http://spring-cloud-order-service-provider/order/data/getTodayFinishOrderNum/" + id;
        return restTemplate.getForObject(url, Integer.class);
    }

    // 服务降级方法 ,这里参数与返回值需要原方法保持一直
    public Integer getTodayStatisticFallbackB(Integer id) {
        return -1;
    }
}

配置方式二:配置文件方式

hystrix:
  command:
    default:
      circuitBreaker:
        forceOpen: false # 是否强制打开熔断器,如果设置true,表示打开熔断器,然后拒绝所有请求,默认是false的
        errorThresholdPercentage: 50   #失败比例阈值,默认值50%
        sleepWindowInMilliseconds: 1000  #熔断后休眠时⻓,默认值5秒
        requestVolumeThreshold: 3   #最小请求数,默认值是20
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000  #超时设置,默认为1秒

欢迎关注我的公众号:程序员L札记

更多原创文章,请扫码关注我的微信公众号
更多原创文章,请扫码关注我的微信公众号

分类:

后端

标签:

Java

作者介绍

程序员L札记
V1