SpringCloud中的Hystrix
为什么要学这个?你不想面试的时候多装两个逼多拿两千块钱?
Hystrix现在已经停止维护,有兴趣可以看我的另一篇博客链接: link.
首先我们先想一想分布式微服务可能存在哪些问题。
服务雪崩
多个微服务之间的调用,假设A调用B,B又调用C和D服务,C和D又分别调用其他的微服务。
那么此次请求的涉及面就越来越大,这就是“扇出”!假如当前D这个服务调用时间过长或者挂掉了
那么对于A的以后调用就会占据越来越多的资源,从而引起系统崩溃。
简单来说,当你发现一个模块故障了之后,但是这个模块还会继续的接收请求,然而这个故障模块还调用了其他很多的模块,这样就发生了级联故障,这就是雪崩。
这里我们学习一下Hystrix
Hystrix是一个用于分布式系统的延迟的容错的开源组件,在分布式系统里不可避免会遇到调用失败,超时,异常等,Hystrix可以保证在一个依赖的服务出问题的时候,不会导致整体服务失败,避免雪崩。
1.服务降级
服务不可用了,我不能让线程一直等待在这里消耗资源,我要给服务调用方返回一个友好提示(fallback)。
触发服务降级的场景。
- 程序运行异常
- 超时
- 服务熔断
- 线程池/信号量打满
代码案例
服务端8001的两个服务,Controller层和消费端代码我就不贴出来了,只说最关键的部分。
案例场景:
用JeMeter压测工具对paymentInfo_TimeOut()方法进行压测,假设现在有2万个线程访问paymentInfo_TimeOut()方法,每个线程进来呢都会等待3S。大家可以想像一下,现在服务器的信号量肯定已经打满了。TomCat的线程池也就10个而已。那么现在我们去访问paymentInfo_ok()这个正常的接口,你就会发现也会出现等待的情况甚至触发了Feign的调用超时返回一个报错的页面,客户体验十分不好。也就说明整个服务都已经被paymentInfo_TimeOut()给 拖慢了
@Service
public class DeptServiceImpl implements DeptService {
@Override
//正常访问没有异常的方法
public String paymentInfo_ok(Integer id) {
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok:id:"+id;
}
@Override
//等待3S返回的方法
public String paymentInfo_TimeOut(Integer id) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut:id:"+id+"耗时3秒";
}
}
解决问题三大维度:
- 服务8001超时了,调用者不能一直等待,返回错误页面,必须要有服务降级
- 服务8001宕机或异常了,调用者不能一直等待,返回错误页面,必须要有服务降级
- 服务8001Ok了,但调用方自己出故障了或有自我要求(自己的等待时间小于服务8001),自己处理降级
开始解决:
@EnableCircuitBreaker //添加对Hystrix的支持
public class HystrixDeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDeptProvider_8001.class, args)
修改业务类,对paymentInfo_TimeOut()方法进行整改,加上 @HystrixCommand注解,其中有两个参数:
1.fallbackMethod :表示我这个方法如果出问题了,那我马上去执行我预先写好的fallback方法,马上给调用者返回一个友好提示。
2.commandProperties :是一个数组,里面可以写很多参数,表示对paymentInfo_TimeOut()方法的一系列控制。我这里写例子中的含义是:一个线程来访问paymentInfo_TimeOut(),最长执行时间是2秒,如果超过2秒都还没执行完,我就算执行出错,立刻进行服务降级,执行fallbackMethod!
@Service
public class DeptServiceImpl implements DeptService {
@Override
//正常访问没有异常的方法
public String paymentInfo_ok(Integer id) {
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_ok:id:"+id;
}
@Override
//等待3S返回的方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "2000")
})
public String paymentInfo_TimeOut(Integer id) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//int a = 1/0;
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut:id:"+id+"耗时3秒";
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "8001服务调用失败,请稍后再试";
}
}
测试发现,两秒钟会直接返回“8001服务调用失败,请稍后再试”。 如果我们把Thread.sleep(3000);注释掉,手动的写一个运行时异常int a = 1/0;,也会有同样的效果。
好,服务端的降级保护做完了后。我们想一个这样的场景,paymentInfo_TimeOut()方法在经过降级保护后,超时时间最大为2s,超过两秒我就直接降级返回了。那这个时候我们服务消费者是通过OpenFeign来调用我们这个paymentInfo_TimeOut()方法,我们知道OpenFeign也是有自己的调用超时时间的,默认是1s。不知道的同学看看这篇文章: SpringCloud中的OpenFeign的超时控制和日志增强.
那么这个时候其实对于调用者来说,服务调用依然是超时的,会返回错误信息。那我们知道,其实在服务客户端,我们也是需要去做这样一个服务降级保护处理的。
开始解决客户端的降级处理:
在客户端的配置文件中开始基于feign的降级处理
feign:
hystrix:
enabled: true
在启动类加上@EnableHystrix注解
@EnableHystrix
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
在客户端的Controller层依葫芦画瓢,但是定义的超时时间为1S。这样Feign在调用超时后,就会被直接降级。避免报错。同理,如果是运行时异常,也会有同样的效果。
@RestController
public class OpenFeignController {
@Autowired
private MyTestOpenFeign01 myTestOpenFeign01;
@GetMapping("/paymentInfo_TimeOut/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1000")
})
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
return myTestOpenFeign01.paymentInfo_TimeOut(id);
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "客户端调用失败,请稍后再试";
}
1.1:定义全局服务降级
如果我有一百个方法需要降级,上面的写法是不是要写100遍?既不好维护,代码又太膨胀。
@DefaultProperties+@HystrixCommand:表示在此类中,所有打了@HystrixCommand的方法,都会走到defaultFallback降级方法中去!
但如果有个别方法比如代码中的paymentInfo_TimeOut()方法指定了自己的降级逻辑和目标方法的话,它就走自己的方法!
@RestController
@DefaultProperties(defaultFallback = "")
public class OpenFeignController {
@Autowired
private MyTestOpenFeign01 myTestOpenFeign01;
@GetMapping("/paymentInfo_TimeOut/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1000")
})
public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
return myTestOpenFeign01.paymentInfo_TimeOut(id);
}
public String paymentInfo_TimeOutHandler(Integer id) {
return "客户端调用失败,请稍后再试";
}
public String defaultHandler() {
return "客户端调用失败,请稍后再试(全局降级方法)";
}
@HystrixCommand
@GetMapping("/paymentInfo_ok/{id}")
public String paymentInfo_ok(@PathVariable("id") Integer id){
return myTestOpenFeign01.paymentInfo_ok(id);
}
1.2:降级方法与业务代码解耦
上面的各种案例你会发现,业务代码和降级方法都耦合在一起,这是不符合我们编程规范的!
好,我们现在要进行解耦操作。个干个的,分工明确。
首先声明一点:服务降级基本都是在客户端做的事情,因为如果遇到服务端宕机的情况,降级的事情只能由客户端来完成
下面的代码是客户端中的FeignClient接口代码,我们在@FeignClient注解中加上一个fallback 属性,属性的值是一个类模板对象!也就是说,这个接口中的所有方法,在出错的时候(异常,超时,服务器宕机),都会走到MytestOpenFeignImpl中去! 那MytestOpenFeignImpl是什么呢,是当前接口的实现类!
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallback = MytestOpenFeignImpl.class)
public interface MyTestOpenFeign01 {
@GetMapping("/paymentInfo_ok/{id}")
String paymentInfo_ok(@RequestParam("id")Integer id);
@GetMapping("/paymentInfo_ok/{id}")
String paymentInfo_TimeOut(@RequestParam("id")Integer id);
FeignClient接口的实现类,接口中的每一个方法都会被重写,那么重写过后的方法,就是接口对应的FallBack方法!
@Component
public class MytestOpenFeignImpl implements MyTestOpenFeign01{
@Override
public String paymentInfo_ok(Integer id) {
return "我是paymentInfo_ok接口的FallBack方法";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "我是paymentInfo_TimeOut接口的FallBack方法";
}
2.服务熔断
服务达到最大访问量了,直接拒绝方法,然后调用服务降级的方法给调用方一个友好提示。然后慢慢恢复服务。
链接: 服务熔断
3. 服务限流
高并发请求的情况下,禁止流量全部涌入,限制流量进入服务,如一秒N个。
服务限流我们不用Hystrix来讲解,请看我关于springcloudalibaba中的Sentinel组件
好了 基本已经讲完,欢迎大家评论区指出不足,一起学习进步!
大家看完了点个赞,码字不容易啊。。。