Hystrix是Netflix开源的一款分布式容错框架,其为微服务提供了一整套服务保护、容错机制。从而避免由于个别服务故障而引起的级联故障,即所谓的服务雪崩效应

服务降级
所谓服务降级是指所调用服务发生意外时,调用自己本地方法(即fallback降级方法)返回缺省值。而导致服务降级的原因包括但不限于服务超时、异常、宕机、熔断、服务资源不足(例如线程、信号量等)等。实践过程中,首先在payment服务的POM文件引入Hystrix依赖,如下所示
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | <dependencyManagement><dependencies>
 
 
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-dependencies</artifactId>
 <version>2.2.2.RELEASE</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 
 
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Hoxton.SR1</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 
 </dependencies>
 </dependencyManagement>
 
 <dependencies>
 
 
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 </dependency>
 
 </dependencies>
 
 | 
可通过@HystrixCommand注解,配置该方法所对应的降级方法。具体通过fallbackMethod属性配置一个入参、出参类型一致的降级方法。进一步地,还可以通过commandProperties属性设置相关降级配置。但如果需要对每个@HystrixCommand注解都添加重复的配置,显然十分麻烦。故可以在类上添加@DefaultProperties注解设置该类默认的降级属性配置,其会对该类中添加了@HystrixCommand注解的方法,提供默认的降级配置。特别地对于该类默认的降级方法,一方面通过defaultFallback属性设置;另一方面该降级方法是无入参的,但出参类型需要与被降级的方法保持一致
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | @RestController@RequestMapping("pay2")
 
 @DefaultProperties(defaultFallback = "defaultFallback")
 public class PaymentController2 {
 
 @GetMapping("/test1")
 
 @HystrixCommand(fallbackMethod = "timeoutFallback", commandProperties = {
 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
 })
 public String test1(@RequestParam Integer time) {
 
 try {
 Thread.sleep( time );
 } catch (Exception e) {
 }
 
 String msg = "[Payment Service - test1], time:" + time;
 return msg;
 }
 
 @GetMapping("/test2")
 
 @HystrixCommand
 public String test2(@RequestParam Integer time) {
 
 try {
 Thread.sleep( time );
 } catch (Exception e) {
 }
 
 String msg = "[Payment Service - test2], time:" + time;
 return msg;
 }
 
 @GetMapping("/test3")
 
 @HystrixCommand
 public String test3(@RequestParam Integer num) {
 int result = 10/num;
 String msg = "[Payment Service - test3], num:" + num;
 return msg;
 }
 
 
 
 
 
 
 public String timeoutFallback(Integer time) {
 return "payment服务发生超时, param: time: " + time;
 }
 
 
 
 
 
 public String defaultFallback() {
 return "payment服务暂不可用";
 }
 
 }
 
 | 
最后,在启动类上添加@EnableHystrix注解以启用Hystrix
| 12
 3
 4
 5
 6
 7
 8
 
 | @SpringBootApplication@EnableDiscoveryClient
 @EnableHystrix
 public class PaymentApplication {
 public static void main(String[] args) {
 SpringApplication.run(PaymentApplication.class, args);
 }
 }
 
 | 
测试效果如下,符合预期

服务熔断
Hystrix的服务熔断采用断路器模式的思想进行设计。在该模式中存在如下三种状态
- Closed:关闭状态。接受请求,服务的主逻辑可以被访问调用
- Open:打开状态。拒绝请求,服务的主逻辑无法执行。该服务如果有相应的降级方法则执行降级方法,否则直接抛出错误响应
- Half-Open:半开状态。处于该状态时,其会允许一部分请求流量通过,尝试执行主逻辑。如果发现调用成功则说明当前服务可以被恢复,进而变为Closed状态;否则调用失败,继续变为Open状态
从上可以看到,正是由于Half-Open状态的存在,为服务的自我恢复提供了一种思路。关于上述三种状态的转换逻辑,如下所示。在服务初始阶段,断路器肯定处于Closed状态。在断路器统计的滑动时间窗口内,满足一定的打开条件时,断路器才会进入Open状态。当断路器进入Open状态达到一定时间后会进入Half-Open状态,让一部分请求通过以验证服务是否可以被恢复,并根据验证结果决定进入Closed或Open状态

这里需要补充说明的是,断路器统计的滑动时间窗口可通过 metrics.rollingStats.timeInMilliseconds 进行配置。而断路器的打开条件则是指在该时间窗口内,其接受的请求数、请求的失败率均达到阈值。需要注意的是,这两个阈值必须同时满足。可分别通过 circuitBreaker.requestVolumeThreshold、circuitBreaker.errorThresholdPercentage 进行配置。最后,对于断路器在进入Open状态后需要多长时间才会进入Half-Open状态,则可通过 circuitBreaker.sleepWindowInMilliseconds 进行配置
为了方便验证测试,我们在PaymentController2类继续添加一个新的方法test4
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | @RestController@RequestMapping("pay2")
 
 @DefaultProperties(defaultFallback = "defaultFallback")
 public class PaymentController2 {
 
 ...
 
 @GetMapping("/test4")
 @HystrixCommand( commandProperties = {
 // 打开断路器
 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
 // 断路器统计的滑动时间窗口, Unit: ms
 @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "10000"),
 // 断路器的请求数阈值
 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
 // 断路器的请求失败率阈值, Unit: %
 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"),
 // 断路器进入Open打开状态多长时间后, 允许再次尝试处理部分请求(即Half-Open半开状态), Unit: ms
 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
 })
 public String test4(@RequestParam Integer num) {
 if(num < 50) {
 throw new RuntimeException("非法参数异常");
 }
 
 String msg = "[Payment Service - test4], num:" + num;
 return msg;
 }
 
 
 
 
 
 public String defaultFallback() {
 return "payment服务暂不可用";
 }
 
 }
 
 | 
测试结果如下所示。第①次访问时,可以看到访问正常,执行了主流程;第②次访问时,发送了5个请求且访问结果均失败被降级;然后马上开始第③次访问,从测试结果可以看出由于该服务已经被熔断了,直接走降级方法;等过了一段时间后,服务恢复第④次访问成功

服务限流
Hystrix提供了两种隔离策略:线程池隔离、信号量隔离。这里我们以前者为例介绍如何进行限流,信号量隔离同理。在线程池隔离的场景下,可通过控制线程池相关参数实现流量控制。这样当线程池资源不足时,对于新的请求就进行降级。为了方便验证测试,我们在PaymentController2类继续添加一个新的方法test5
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | @RestController@RequestMapping("pay2")
 
 @DefaultProperties(defaultFallback = "defaultFallback")
 public class PaymentController2 {
 
 ...
 
 @GetMapping("/test5")
 @HystrixCommand(
 commandProperties = {
 // 方法超时配置, Unit: ms
 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "15000"),
 // 资源隔离模式: 线程池隔离
 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")
 },
 threadPoolProperties = {
 // 核心线程数
 @HystrixProperty(name = "coreSize", value = "2"),
 // 等待队列容量, -1表示不启用
 @HystrixProperty(name = "maxQueueSize", value = "-1")
 }
 )
 public String test5(@RequestParam Integer num) {
 if(num < 50) {
 throw new RuntimeException("非法参数异常");
 }
 
 
 try {
 Thread.sleep( 10000 );
 } catch (Exception e) {
 }
 
 String msg = "[Payment Service - test5], num:" + num;
 return msg;
 }
 
 
 
 
 
 public String defaultFallback() {
 return "payment服务暂不可用";
 }
 
 }
 
 | 
对于该服务接口其最大并发量为2。这样同时发送3个请求时,可以看到前2个请求正常处理。但第3个请求由于(线程池)资源不足被限流了,故进行降级处理。测试结果符合预期

服务监控
Hystrix还提供了Dashboard以便通过可视化的方式实现服务监控。这里我们建立一个HystrixDashboard服务对目标服务进行监控。首先在POM中我们引入 spring-cloud-starter-netflix-hystrix-dashboard 依赖,如下所示
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | <dependencyManagement><dependencies>
 
 
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-dependencies</artifactId>
 <version>2.2.2.RELEASE</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 
 
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>Hoxton.SR1</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 
 </dependencies>
 </dependencyManagement>
 
 <dependencies>
 
 
 <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-web</artifactId>
 </dependency>
 
 </dependencies>
 
 | 
然后为该服务添加一个启动类即可,并通过添加@EnableHystrixDashboard注解实现Hystrix Dashboard的启用
| 12
 3
 4
 5
 6
 7
 8
 
 | @SpringBootApplication
 @EnableHystrixDashboard
 public class HystrixDashboardApplication {
 public static void main(String[] args) {
 SpringApplication.run(HystrixDashboardApplication.class, args);
 }
 }
 
 | 
对于HystrixDashboard服务而言,其配置文件如下所示
| 12
 3
 4
 5
 6
 
 | spring:application:
 name: HystrixDashboard
 
 server:
 port: 9111
 
 | 
这里我们以监控payment服务为例展开说明,在此之前我们需要对被监控服务payment进行一些必要的配置。首先需要在POM文件中添加 spring-boot-starter-actuator 依赖
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | <dependencies>
 
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 <version>2.2.2.RELEASE</version>
 </dependency>
 
 </dependencies>
 
 | 
与此同时,需要配置Actuator以开启 hystrix.stream 端点,相关配置如下所示
| 12
 3
 4
 5
 6
 7
 
 | management:
 endpoints:
 web:
 exposure:
 include: hystrix.stream
 base-path: /actuator
 
 | 
启动payment、HystrixDashboard服务,分别使用8006、9111端口。首先访问payment服务的 hystrix.stream 端点验证是否有相关监控数据,打开 http://localhost:8006/actuator/hystrix.stream 页面。效果如下,符合预期

然后访问HystrixDashboard服务,打开 http://localhost:9111/hystrix 页面。并在页面上填写被监控服务的 hystrix.stream 端点地址、延迟时间、被监控服务名称

最后,点击Monitor Stream开启监控,效果如下所示

参考文献
- Spring微服务实战 John Carnell著