SpringCloud下基于Hystrix的服务实践
Hystrix是Netflix开源的一款分布式容错框架,其为微服务提供了一整套服务保护、容错机制。从而避免由于个别服务故障而引起的级联故障,即所谓的服务雪崩效应
服务降级
所谓服务降级是指所调用服务发生意外时,调用自己本地方法(即fallback降级方法)返回缺省值。而导致服务降级的原因包括但不限于服务超时、异常、宕机、熔断、服务资源不足(例如线程、信号量等)等。实践过程中,首先在payment服务的POM文件引入Hystrix依赖,如下所示
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
dependencies>
可通过@HystrixCommand注解,配置该方法所对应的降级方法。具体通过fallbackMethod属性配置一个入参、出参类型一致的降级方法。进一步地,还可以通过commandProperties属性设置相关降级配置。但如果需要对每个@HystrixCommand注解都添加重复的配置,显然十分麻烦。故可以在类上添加@DefaultProperties注解设置该类默认的降级属性配置,其会对该类中添加了@HystrixCommand注解的方法,提供默认的降级配置。特别地对于该类默认的降级方法,一方面通过defaultFallback属性设置;另一方面该降级方法是无入参的,但出参类型需要与被降级的方法保持一致
@RestController
@RequestMapping("pay2")
// 该类默认的降级配置
@DefaultProperties(defaultFallback = "defaultFallback")
public class PaymentController2 {
@GetMapping("/test1")
// 单独配置该方法的降级方法, 并将方法的超时配置由默认1s改为5s
@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")
// 使用@DefaultProperties注解提供的默认降级相关配置
@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")
// 使用@DefaultProperties注解提供的默认降级相关配置
@HystrixCommand
public String test3(@RequestParam Integer num) {
int result = 10/num;
String msg = "[Payment Service - test3], num:" + num;
return msg;
}
/**
* 超时降级方法
* @param time
* @return
*/
public String timeoutFallback(Integer time) {
return "payment服务发生超时, param: time: " + time;
}
/**
* 默认降级方法
* @return
*/
public String defaultFallback() {
return "payment服务暂不可用";
}
}
最后,在启动类上添加@EnableHystrix注解以启用Hystrix
@SpringBootApplication
@EnableDiscoveryClient // 使用Consul作为注册中心时使用
@EnableHystrix // 启用Hystrix
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
@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;
}
/**
* 默认降级方法
* @return
*/
public String defaultFallback() {
return "payment服务暂不可用";
}
}
测试结果如下所示。第①次访问时,可以看到访问正常,执行了主流程;第②次访问时,发送了5个请求且访问结果均失败被降级;然后马上开始第③次访问,从测试结果可以看出由于该服务已经被熔断了,直接走降级方法;等过了一段时间后,服务恢复第④次访问成功
服务限流
Hystrix提供了两种隔离策略:线程池隔离、信号量隔离。这里我们以前者为例介绍如何进行限流,信号量隔离同理。在线程池隔离的场景下,可通过控制线程池相关参数实现流量控制。这样当线程池资源不足时,对于新的请求就进行降级。为了方便验证测试,我们在PaymentController2类继续添加一个新的方法test5
@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;
}
/**
* 默认降级方法
* @return
*/
public String defaultFallback() {
return "payment服务暂不可用";
}
}
对于该服务接口其最大并发量为2。这样同时发送3个请求时,可以看到前2个请求正常处理。但第3个请求由于(线程池)资源不足被限流了,故进行降级处理。测试结果符合预期
服务监控
Hystrix还提供了Dashboard以便通过可视化的方式实现服务监控。这里我们建立一个HystrixDashboard服务对目标服务进行监控。首先在POM中我们引入 spring-cloud-starter-netflix-hystrix-dashboard 依赖,如下所示
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
然后为该服务添加一个启动类即可,并通过添加@EnableHystrixDashboard注解实现Hystrix Dashboard的启用
@SpringBootApplication
// 启用 Hystrix Dashboard
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
对于HystrixDashboard服务而言,其配置文件如下所示
spring:
application:
name: HystrixDashboard
server:
port: 9111
这里我们以监控payment服务为例展开说明,在此之前我们需要对被监控服务payment进行一些必要的配置。首先需要在POM文件中添加 spring-boot-starter-actuator 依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
<version>2.2.2.RELEASEversion>
dependency>
dependencies>
与此同时,需要配置Actuator以开启 hystrix.stream 端点,相关配置如下所示
# Actuator配置: 开启 hystrix.stream 端点
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著