一、Spring Cloud Hystrix简介

在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。Hystrix实现了断路器模式,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

二、准备工作

依赖
在上一节📃 Ribbon 负载均衡与服务调用ribbon-server模块中加入依赖:

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

在启动类中加入@EnableCircuitBreaker注解:

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class RibbonServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonServerApplication.class, args);
    }
}

三、服务降级

先了解下@HystrixCommand中的常用参数:

  • fallbackMethod 指定服务降级处理方法;
  • ignoreExceptions 忽略某些异常,不发生服务降级;
  • commandKey 命令名称,用于区分不同的命令;
  • groupKey 分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息;
  • threadPoolKey 线程池名称,用于划分线程池。

给之前在ribbon-server模块中创建的控制器方法前添加@HystrixCommand注解:

    @HystrixCommand(fallbackMethod = "getDefault")
    @GetMapping("/test/{id}")
    @ResponseBody
    public Object testHystrix(@PathVariable int id) {
        return restTemplate.getForObject(userServiceUrl + "/test/{1}", Object.class, id);
    }

    public Object getDefault(int id) {
        Map<String, String> map = new HashMap<>();
        map.put("code", "-1");
        map.put("message", "failed");
        map.put("data", "");
        return map;
    }

:::info 注意:
fallbackMethod 指定的方法需要与原方法参数表相同 :::

演示:
如果Ribbon客户端服务正常启动,调用接口正常返回:
Snipaste_2021-01-25_22-08-13.png

如果关闭Ribbon客户端,调用接口返回降级后的结果:
Snipaste_2021-01-25_22-04-54.png

四、异常忽略

可以在@HystrixCommand中配置ignoreExceptions,以忽略某些类型的异常。

    @GetMapping("/test/{id}")
    @HystrixCommand(fallbackMethod = "getDefault", ignoreExceptions = {NullPointerException.class})
    public Object test(@PathVariable Long id) {
        if (id == 1) {
            throw new IndexOutOfBoundsException();
        } else if (id == 2) {
            throw new NullPointerException();
        }
        return restTemplate.getForObject(userServiceUrl + "/test/test", Object.class);
    }

    public Object getDefault(@PathVariable Long id, Throwable e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "-1");
        map.put("message", "failed");
        map.put("data", "");
        return map;
    }

这样的话,访问:

  • http://localhost:8201/test/test/1 报-1(走getDefault方法)
  • http://localhost:8201/test/test/2 报500(不进行服务降级,直接抛出错误)
  • http://localhost:8201/test/test/3 正常返回结果(走restTemplate.getForObject方法)

五、请求缓存

  • @CacheResult 开启缓存,默认所有参数作为缓存的key,cacheKeyMethod可以通过返回String类型的方法指定key;
  • @CacheKey 指定缓存的key,可以指定参数或指定参数中的属性值为缓存key,cacheKeyMethod还可以通过返回String类型的方法指定;
  • @CacheRemove 移除缓存,需要指定commandKey。

首先创建一个服务,在testCache方法中加入@CacheResult注解:

@Service
public class TestService {
    private Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Value("${service-url.user-service}")
    private String userServiceUrl;

    @Autowired
    private RestTemplate restTemplate;

    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(fallbackMethod = "getDefault")
    public Object testCache(Long id) {
        LOGGER.info("getCacheKey id:{}", id);
        return restTemplate.getForObject(userServiceUrl + "/test/test", Object.class);
    }

    /**
     * 为缓存生成key的方法
     */
    public String getCacheKey(Long id) {
        return String.valueOf(id);
    }

    public Object getDefault(@PathVariable Long id, Throwable e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "-1");
        map.put("message", "failed");
        map.put("data", "");
        return map;
    }
}

在控制器中调用此服务:

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private UserService userService;

    @GetMapping("/test/{id}")
    public Object test(@PathVariable Long id) {
        userService.testCache(id);
        userService.testCache(id);
        userService.testCache(id);
        return "success";
    }
}

访问http://localhost:8201/test/test/1,可以看到,调用了3次testCache,但是控制台只打印了一次getCacheKey,说明后面两次都走的缓存。
016.png

清除缓存

在TestService中添加一个清除缓存的方法,添加@CacheRemove注解:

    @CacheRemove(commandKey = "testCache", cacheKeyMethod = "getCacheKey")
    @HystrixCommand
    public Object removeCache(Long id) {
        LOGGER.info("removeCache id:{}", id);
        return restTemplate.getForObject(userServiceUrl + "/test/test", Object.class);
    }

在控制器中调用:

    @GetMapping("/remove/{id}")
    public Object testRemove(@PathVariable Long id) {
        userService.testCache(id);
        userService.removeCache(id);
        userService.testCache(id);
        return "success";
    }

访问http://localhost:8201/test/remove/1,在控制台中看到打印结果:
017.png

六、请求合并

微服务系统中的服务间通信,需要通过远程调用来实现,随着调用次数越来越多,占用线程资源也会越来越多。Hystrix中提供了@HystrixCollapser用于合并请求,从而达到减少通信消耗及线程数量的效果。

先了解下@HystrixCollapser中的常用参数:

  • batchMethod 用于设置请求合并的方法
  • collapserProperties 请求合并属性,用于控制实例属性,有很多
  • timerDelayInMilliseconds collapserProperties中的属性,用于控制每隔多少时间合并一次请求

七、Hystrix Dashboard

Hystrix Dashboard是Spring Cloud中查看Hystrix实例执行情况的一种仪表盘组件,支持查看单个实例和查看集群实例。

首先新创建一个模块hystrix-dashboard,添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

添加配置:

server:
  port: 8301
spring:
  application:
    name: hystrix-dashboard
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/

在启动类中添加@EnableHystrixDashboard注解:

@EnableHystrixDashboard
@EnableDiscoveryClient
@SpringBootApplication
public class HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}

启动hystrix-dashboard服务,在浏览器中输入 http://localhost:8301/hystrix 打开可以看到Hystrix Dashboard界面:
019.png

单个实例监控

在ribbon-service模块中添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

在ribbon-service模块中添加配置:

management:
  endpoints:
    web:
      exposure:
        include: "hystrix.stream"

其中hystrix.stream是ribbon-service暴露的hystrix监控端点

启动ribbon-service服务,在Hystrix Dashboard中输入http://localhost:8201/actuator/hystrix.stream
020.png

进入到监控界面,调用几次http://localhost:8201/test/test/1接口,可以看到:
021.png

集群实例监控

创建一个turbine-service模块,添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

添加配置:

server:
  port: 8401
spring:
  application:
    name: turbine-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8001/eureka/
turbine:
  app-config: hystrix-service #指定需要收集信息的服务名称
  cluster-name-expression: new String('default') #指定服务所属集群
  combine-host-port: true #以主机名和端口号来区分服务

在启动类中添加@EnableTurbine注解:

@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

启动turbine-service服务,在Hystrix Dashboard中输入http://localhost:8401/turbine.stream
022.png

进入到监控界面,调用几次http://localhost:8201/test/test/1接口,可以看到:
023.png

Hystrix Dashboard 图表解读

图表解读如下,需要注意的是,小球代表该实例健康状态及流量情况,颜色越显眼,表示实例越不健康,小球越大,表示实例流量越大。曲线表示Hystrix实例的实时流量变化。
018.png

来自:Hystrix Dashboard 图表解读

八、Hystrix的常用配置

全局配置

hystrix:
  command: #用于控制HystrixCommand的行为
    default:
      execution:
        isolation:
          strategy: THREAD #控制HystrixCommand的隔离策略,THREAD->线程池隔离策略(默认),SEMAPHORE->信号量隔离策略
          thread:
            timeoutInMilliseconds: 1000 #配置HystrixCommand执行的超时时间,执行超过该时间会进行服务降级处理
            interruptOnTimeout: true #配置HystrixCommand执行超时的时候是否要中断
            interruptOnCancel: true #配置HystrixCommand执行被取消的时候是否要中断
          timeout:
            enabled: true #配置HystrixCommand的执行是否启用超时时间
          semaphore:
            maxConcurrentRequests: 10 #当使用信号量隔离策略时,用来控制并发量的大小,超过该并发量的请求会被拒绝
      fallback:
        enabled: true #用于控制是否启用服务降级
      circuitBreaker: #用于控制HystrixCircuitBreaker的行为
        enabled: true #用于控制断路器是否跟踪健康状况以及熔断请求
        requestVolumeThreshold: 20 #超过该请求数的请求会被拒绝
        forceOpen: false #强制打开断路器,拒绝所有请求
        forceClosed: false #强制关闭断路器,接收所有请求
      requestCache:
        enabled: true #用于控制是否开启请求缓存
  collapser: #用于控制HystrixCollapser的执行行为
    default:
      maxRequestsInBatch: 100 #控制一次合并请求合并的最大请求数
      timerDelayinMilliseconds: 10 #控制多少毫秒内的请求会被合并成一个
      requestCache:
        enabled: true #控制合并请求是否开启缓存
  threadpool: #用于控制HystrixCommand执行所在线程池的行为
    default:
      coreSize: 10 #线程池的核心线程数
      maximumSize: 10 #线程池的最大线程数,超过该线程数的请求会被拒绝
      maxQueueSize: -1 #用于设置线程池的最大队列大小,-1采用SynchronousQueue,其他正数采用LinkedBlockingQueue
      queueSizeRejectionThreshold: 5 #用于设置线程池队列的拒绝阀值,由于LinkedBlockingQueue不能动态改版大小,使用时需要用该参数来控制线程数Copy to clipboardErrorCopied

实例配置

实例配置只需要将全局配置中的default换成与之对应的key即可。

hystrix:
  command:
    HystrixComandKey: #将default换成HystrixComrnandKey
      execution:
        isolation:
          strategy: THREAD
  collapser:
    HystrixCollapserKey: #将default换成HystrixCollapserKey
      maxRequestsInBatch: 100
  threadpool:
    HystrixThreadPoolKey: #将default换成HystrixThreadPoolKey
      coreSize: 10Copy to clipboardErrorCopied

配置文件中相关key的说明

  • HystrixComandKey对应@HystrixCommand中的commandKey属性;
  • HystrixCollapserKey对应@HystrixCollapser注解中的collapserKey属性;
  • HystrixThreadPoolKey对应@HystrixCommand中的threadPoolKey属性。

常见错误

Request caching is not available. Maybe you need to initialize the HystrixRequestContext?

在缓存使用过程中,我们需要在每次使用缓存的请求前后对HystrixRequestContext进行初始化和关闭,否则会出现如下异常:

java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext?

我们可以通过使用过滤器,在每个请求前后初始化和关闭HystrixRequestContext来解决该问题:

@Component
@WebFilter(urlPatterns = "/*",asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            context.close();
        }
    }
}

Invalid character found in method name. HTTP method names must be tokens

错误原因是由于在本地测试的时候,使用了https请求,将其改为http即可

参考资料