why Hystrix

在微服务架构中,我们将系统拆分成了很多服务单元,各单元的应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身间题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。

所以我们引入了断路器,类似于物理上的电路,当电流过载时,就断开电路,就是我们俗称的“跳闸”。同理,服务间的调用也是如此,当不断的出现服务延迟、故障等影响到系统性能的调用,就把这个服务调用切断!

Hystrix 是用于分布式场景下服务熔断、降级的开源 Java 库。它的主要作用有线程隔离,熔断,降级和监控报警.

Spring Cloud Hystrix 实现了断路器、线程隔离等一系列服务保护功能。

它也是基于 Netflix 的开源框架 Hystrix 实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

Hystrix 入门

微服务集成 hystrix

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

启动类加注解 @EnableCircuitBreaker 启动断路器功能

1.方法级别的配置

  1. /**
  2. * @author: michael
  3. * @create: 2021/03/30
  4. */
  5. @RestController
  6. @RequestMapping("/methodHystrix")
  7. @SuppressWarnings("unchecked")
  8. public class MethodHystrixController {
  9. @GetMapping("getUserInfo")
  10. @HystrixCommand(commandProperties = {
  11. @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000"), //统计窗口时间
  12. @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //启用熔断功能
  13. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), //20个请求失败触发熔断
  14. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), //请求错误率超过60%触发熔断
  15. @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "300000"),//熔断后开始尝试恢复的时间
  16. })
  17. public ResponseEntity<UserVO> getUserInfo() {
  18. long currentTimeMillis = System.currentTimeMillis() % 10;
  19. System.out.println(currentTimeMillis);
  20. if (currentTimeMillis % 2 == 0) {
  21. return new ResponseEntity(new UserVO("demon", (int) currentTimeMillis), HttpStatus.OK);
  22. } else {
  23. throw new RuntimeException("illegal number");
  24. }
  25. }
  26. public ResponseEntity<UserVO> fallBack() {
  27. return new ResponseEntity(new UserVO("defaultUser", 1), HttpStatus.OK);
  28. }
  29. }

metrics.rollingStats.timeInMilliseconds:统计时间窗
circuitBreaker.requestVolumeThreshold:统计时间窗内触发熔断的请求失败次数
circuitBreaker.errorThresholdPercentage:统计时间窗内触发熔断的请求比例
circuitBreaker.sleepWindowInMilliseconds:熔断后恢复的时间
如果配置了fallbackMethod,当正常方法异常时,会调用默认的 fallback 方;

2. 全局配置

default 是全局熔断的配置,这个配置的优先级低于代码注解的配置

  1. hystrix:
  2. command:
  3. default: # commandKey,局部配置中即是方法名,这里default即是全局
  4. circuitBreaker:
  5. enabled: true # 启动熔断
  6. requestVolumeThreshold: 20 # 20个请求触发熔断判断
  7. errorThresholdPercentage: 60 # 请求错误率超过60%触发熔断
  8. sleepWindowInMilliseconds: 300000 # 熔断后开始尝试恢复的时间
  9. getUserName: # 单独对某个方法进行配置
  10. circuitBreaker:
  11. enabled: true # 启动熔断
  12. requestVolumeThreshold: 20 # 20个请求触发熔断判断
  13. errorThresholdPercentage: 60 # 请求错误率超过60%触发熔断
  14. sleepWindowInMilliseconds: 300000 # 熔断后开始尝试恢复的时间

全局配置后,如果要在那个方法上实现熔断的功能,只需要在方法上面加注解 @HystrixCommand

3.类级别

如果每个方法上都要配置,会存在代码冗余

  • @DefaultProperties是一个类方法级别的注解
  • defaultFallback 可以指定该类中所有方法在发生服务降级的时候,执行的本地fallback函数。
  • 需要我们在一个类中定义一个fallback函数,如:commonFallBac

    1. @DefaultProperties(defaultFallback = "commonFallBack")
    2. public class BaseController {
    3. public E6Wrapper commonFallBack() {
    4. return E6WrapperUtil.error("hystrix 触发熔断");
    5. }
    6. }

    其他controller可以继承这个 baseController ,在需要做服务降级的请求上加 @HystrixCommand 注解即可

4.集成 feign

服务降级有两种方式:一种是@DefaultProperties 这种类级别的,另外一种是 @HystrixCommand 这种方法级别的。实际使用中,重点接口建议使用自定义的fallback 处理。
在和与feign 一起使用时,作为服务的调用者,要考虑当服务提供者出现异常,服务降级之后。如何不影响服务提供者。如果是服务提供者,要保证自己方法异常时,如何快速降级,避免服务提供者崩溃。

pom 依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-openfeign</artifactId>
  8. </dependency>

开启feign hystrix 配置

  1. feign:
  2. hystrix:
  3. enabled: true

服务提供者客户端编写

  1. @FeignClient(name = "FEIGN-SERVICE", fallback = UserRpcFallBackService.class)
  2. public interface UserConsumerService {
  3. @RequestMapping(value = "/user/getUserInfo", method = RequestMethod.GET)
  4. ResponseEntity<UserVO> getUserInfo(@RequestParam("userId") Integer userId);
  5. }

服务提供者失败,回调

  1. @Service
  2. public class UserRpcFallBackService implements UserConsumerService {
  3. @Override
  4. public ResponseEntity<UserVO> getUserInfo(Integer userId) {
  5. return ResponseEntity.ok(new UserVO("demon","188"));
  6. }
  7. }

服务提供者 restful 接口

  1. @RestController
  2. @RequestMapping
  3. public class UserController {
  4. @Autowired
  5. private UserService userRpcService;
  6. @RequestMapping(value = "/user/getUserInfo",method = RequestMethod.GET)
  7. ResponseEntity<UserVO> getUserInfo(@RequestParam("userId") Integer userId){
  8. return userRpcService.getUserInfo(userId);
  9. }
  10. }
  11. @Service
  12. public class UserService {
  13. public ResponseEntity<UserVO> getUserInfo(Integer userId) {
  14. if (userId % 2 == 0) {
  15. return ResponseEntity.ok(new UserVO("michel", userId.toString()));
  16. } else {
  17. throw new RuntimeException("mock exception");
  18. }
  19. }
  20. }

测试发现 UserService 接口异常或者FEIGN-SERVICE 服务挂掉后,会走fallback 接口。