程序员L札记
2022/05/05阅读:20主题:橙心
Spring Cloud Hystrix实战(一)
概述
在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。
服务降级
当出现以下情况,服务器忙,不让客户端等待并立刻返回一个友好提示,fallback
-
程序运行异常 -
超时 -
服务熔断触发服务降级 -
线程池/信号量打满
注意:服务降级并不代表断路器打开
服务熔断
当服务器请求超过最大数,直接拒绝访问,然后调用服务降级方法并返回一个友好提示
服务限流
秒杀高并发等操作,严禁拥挤,排队有序进行
基础用法
-
Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
-
添加@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来防止由一个服务引起的级联故障来增加系统的弹性。例如我们的船舱,我们船就一个船舱的话,如果破了一个洞,然后海水就慢慢灌满了整艘船,到最后船就沉下去了,但是如果我们将整艘船隔成若干个船舱,像下图这样,如果船破了一个洞,海水顶多会灌满一个船舱,不会导致整艘船沉下去。

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

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

这样方法的调用就隔离开了,两个互不影响。
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通过一个请求来验证。

整个流程,有一些参数我们是可以根据业务来改动:
-
当出现错误的时候,会开启一个默认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札记

作者介绍