Hystrix服务降级熔断限流
SpringCloud,已停更
前言
分布式系统面临的问题,复杂分布式体系结构中的应用程序,有数10个依赖关系(feign调用), 每个依赖关系在某些时候将不可避免地失败。低耦合就是微服务之间关系少。当某个微服务崩了后,其他有依赖关系的微服务请求不到也会崩,多个就会服务雪崩。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用购应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒内饱和。比失败更槽糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资原紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。所以,当你发现一个模块下的某个实例失败后,这时候这个块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级取故师,或者叫雪崩。
Hystrix简介
在微服务场景中,通常会有很多层的服务调用。如果一个底层服务出现问题,故障会被向上传播给用户。我们需要一种机制,当底层服务不可用时,可以阻断故障的传播。这就是断路器的作用。他是系统服务稳定性的最后一重保障。
Hystrix是一个用于处理分布式系统延迟和容错的开源库,在分布式系统理,许多依赖不可避免的会调用失败,比如超时、异常等
Hystrix能够保证在一个依赖出可题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故牌监控(类以熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中蔓延,乃至雪崩。
官网:https://github.com/Netflix/hystrix/wiki
github:https://github.com/Netflix/hystrix
Hystrix工作流程:https://github.com/Netflix/Hystrix/wiki/How-it-Works
Hystrix的作用
- 对通过第三方客户端库访问的依赖项(通常是通过网络)的延迟和故障进行保护和控制。
- 在复杂的分布式系统中阻止级联故障。
- 快速失败,快速恢复。
- 回退,尽可能优雅地降级。
- 启用近实时监控、警报和操作控制。
重要概念
服务降级
fallback
设置备选方案,服务器忙,请稍后再试,不让客户端(微服务或者前端)等待并立刻返回一个友好提示,fallback。避免多依赖中一直等待占用系统资源,防止雪崩。
哪些情况会发出降级:
- 程序运行异常
- 访问超时
- 服务熔断触发服务降级
- 线程池/信号量也会导致服务降级
服务熔断
break
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务的降级->进而熔断->恢复调用链路
服务限流
flowlimit
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
OpenFeign+Hystrix使用
依赖:
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
服务提供者:这里看看service就行,controller省略。
/**
* @author zzyy
* @create 2020/3/6 22:23
**/
@Service
public class PaymentService {
/**
* 正常访问
*
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_OK,id:" + id + "\t" + "O(∩_∩)O哈哈~";
}
/**
* 超时访问
*
* @param id
* @return
*/
public String paymentInfo_TimeOut(Integer id) {
///int age = 10 / 0;
int timeNumber = 5;
try {
// 暂停5秒钟
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" +
"O(∩_∩)O哈哈~ 耗时(秒)" + timeNumber;
}
feign接口:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixFeign {
/**
* 正常访问
* @param id
* @return
*/
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
/**
* 超时访问
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
服务调用者:
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixFegin paymentHystrixFegin;
/**
* 正常访问
* http://localhost/consumer/payment/hystrix/ok/32
*
* @param id
* @return
*/
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentHystrixFegin.paymentInfo_OK(id);
}
/**
* 超时访问
* http://localhost/consumer/payment/hystrix/timeout/32
*
* @param id
* @return
*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
return paymentHystrixFegin.paymentInfo_TimeOut(id);
}
}
超时访问方法设置5秒,高并发下会阻塞线程,等待很慢,甚至拖累ok不超时方法。
服务降级
向调用方返回一个符合预期的、可处理的备选响应(FallBack)。实际开发是返回ResponseResult<Void>(降级状态码,message)
。
服务提供者
调用降级备选方案的触发条件:
- 超时异常,这里模拟接受3秒,运行5秒
- 运行错误,模拟int age = 10/0
- 宕机
服务提供者添加备选方法:
payment_TimeOutHandler是备选方法名,设置自身调用超时时间的峰值3秒,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback。
@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
/**
* 超时访问
* HystrixCommand:一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbckMethod调用类中的指定方法
* execution.isolation.thread.timeoutInMilliseconds:线程超时时间3秒钟
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})//3秒内就是正常逻辑,不然就调用payment_TimeOutHandler方法返回
public String paymentInfo_TimeOut(Integer id) {
//int age = 10 / 0;
int timeNumber = 5;
try {
// 暂停5秒钟
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeOut,id:" + id + "\t" +
"O(∩_∩)O哈哈~ 耗时(秒)" + timeNumber;
}
/**
* 兜底方案
*
* @param id
* @return
*/
public String payment_TimeOutHandler(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " 系统繁忙或运行错误,请稍后重试,id:" + id + "\t" + "o(╥﹏╥)o";
}
启动类添加注解@EnableCircuitBreaker
激活降级。
服务不可用时,调用降级方法payment_TimeOutHandler()返回给服务消费者。
服务消费者
调用降级备选方案的触发条件:
- 消费者调用feign接口规定为1.5秒内正常,如果服务提供者运行时间超过1.5秒,则触发降级
- 运行错误,模拟int age = 10/0
- 宕机
添加yml配置,开启hystrix。
feign:
hystrix:
# 在feign中开启Hystrix
enabled: true
controller修改添加降级备选方法:
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
触发降级则调用paymentTimeOutFallbackMethod方法返回,这里设置调用feign方法超过1.5秒则触发。
/**
* 超时访问
* http://localhost/consumer/payment/hystrix/timeout/32
*
* @param id
* @return
*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
return paymentHystrixFegin.paymentInfo_TimeOut(id);
}
/**
* 超时方法fallback
* @param id
* @return
*/
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
启动类注入注解@EnableHystrix
全局统一服务降级
除了N个别重要的核心业务外,其它普通的可以通过@DefalultProperties(defaultFallback”)
统一跳转到统一处埋结果页面。
避免代码冗余,合理减少了代码量。
服务消费者controller中,添加@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
注解,如果单个方法上没有对应降级配置,默认调用payment_Global_FallbackMethod方法来降级。但是controller方法需要@HystrixCommand
注解。
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixFeign paymentHystrixFeign;
/**
* 超时访问
* http://localhost/consumer/payment/hystrix/timeout/32
*
* @param id
* @return
*/
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
// @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
// })
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
//int age = 10/0;
return paymentHystrixFegin.paymentInfo_TimeOut(id);
}
/**
* 全局fallback
*
* @return
*/
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后重试.o(╥﹏╥)o";
}
}
feign通配服务降级
重点,开发大多使用这个方法,写在对应的feign模块中即可。
feign模块
对应feign接口添加fallback = PaymentFeignFallback.class
,指定统一fallback方法。
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFeignFallback.class)//服务名可小写
public interface PaymentHystrixFeign {
/**
* 正常访问
* @param id
* @return
*/
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
/**
* 超时访问
*
* @param id
* @return
*/
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
新建fallback包:
implements对应的feign接口,一一对应接口的fallback方法,feign失败则调用该类的fallback方法。实际开发返回ResponseResult<>(对应状态码);
@Component
public class PaymentFeignFallback implements PaymentHystrixFeign {
@Override
public String paymentInfo_OK(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_OK,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_TimeOut,o(╥﹏╥)o";
}
}
消费者
添加yml配置,开启hystrix。
feign:
hystrix:
# 在feign中开启Hystrix
enabled: true
启动类注入注解@EnableHystrix
,其实不用注入。
模拟服务提供者宕机,服务消费者feign访问:
服务熔断
原理
大神论文:https://martinfowler.com/bliki/CircuitBreaker.html
达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bcqrizMy-1669197522584)(…/images/18.png)]
- 熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态
- 熔断关闭:熔断关闭后不会对服务进行熔断
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
触发熔断条件和原理:
当满足一定的阈值的时候(默认10秒钟超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%的请求次数)
到达以上阈值,断路器将会开启
当开启的时候,所有请求都不会进行转发
一段时间之后(默认5秒),这个时候断路器是半开状态,会让其他一个请求进行转发. 如果成功,断路器会关闭,若失败,继续开启.重复4和5。
熔断打开后的原理:
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback.通过断路器,实现了自动地发现错误并把降级逻辑切换为主逻辑。
hystrix为我们实现了自动恢复功能,当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休能时间窗,在这个时间窗内,降级逻辑是临时的主逻辑,当休能时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器继续打开。主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况。
当失败的调用到一定阀值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
例子
服务提供者:
启动类注入:@EnableCircuitBreaker
service:
在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
//====服务熔断
/**
* 在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
* @param id
* @return
*/
@HystrixCommand(
fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),// 时间窗口期/时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")// 失败率达到多少后跳闸
}
)
public String paymentCircuitBreaker(Integer id) {
if (id < 0) {
throw new RuntimeException("*****id不能是负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
// fallback方法
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后重试,o(╥﹏╥)o id:" + id;
}
controller:
/**
* 服务熔断
* http://localhost:8001/payment/circuit/32
* @param id
* @return
*/
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("***result:" + result);
return result;
}
多次正确,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问也不能进行
在10秒窗口期中10次请求有6次是请求失败的,断路器将起作用
之后一直正确就可以恢复调用链路。也可以使用jmeter压力高并发测试。
ALL配置
服务限流
alibaba的Sentinel说明,Sentinel替代。
服务监控hystrixDashboard
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(HystrixDashboard),Hystrix会持续地记录所有通过Hystrix发起的清求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netlix通过hystrix-metrics-event-steam项目实现了对以上指标的监控,Spring Cloud也提供了Hystix Dashboard的整合,对监控内容转化成可视化界面。
依赖:
<!--hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--actuator监控信息完善-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
所有Provider微服务提供类都需要监控依赖部署,以后开发都加上actuator依赖,跟web,test一起。被监控的服务都需要actuator依赖。
启动类:
/**
* @author wzq
* @create 2020/3/7 17:27
**/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class);
}
}
启动成功http://localhost:9001/hystrix:
需要在被监控的微服务启动类添加:
/**
* 此配置是为了服务监控而配置,与服务容错本身无观,springCloud 升级之后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目中配置上下面的servlet即可
* @return
*/
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
在图形页面输入被监控的微服务地址:http://localhost:8001/hystrix.stream
访问正确的controller,熔断Circuit Closed。
访问错误的controller,熔断Circuit Opened。
图形分析:
7色:
1圈:
实心圆:共有两种含义,它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆越大。所以通过实心圆的展示,就可以大量的实例中快速的发现故障实例和高压力实例。
1线:
曲线:用来纪录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势
整体说明:
多微服务监控: