springcloud-一篇搞定Hystrix
Hystrix是什么?
基础概念
服务雪崩
在分布式微服务的系统中,一定是会存在某个A服务调用多个服务,而多个服务也会继续去掉其他服务。因此服务的调用,从调用的结构上看是“扇形”趋势。
因此就会存在一个很重大的问题:如果其中一个服务出现问题了咋办,比如超时,宕机,异常....就会导致前面的调用方在死等着,占据着资源。这还只是一个请求情况下,如果是在高并发的情况下,由于多个服务滞留占用资源,发生蝴蝶效应,导致系统整体服务使用出现缓慢甚至不可以,叫服务雪崩。
这个影响主要有两个:
- 假设A服务出现异常,调用A服务所在的服务链路将不能完成整体业务功能
- 由于A服务出现异常,那些滞留占用资源的服务影响到了其他正常服务的使用。
Hystrix是为了解决第二个问题而出现的延迟和容错的开源库。第一种影响解决不了,本身A服务有问题,所在那条链路肯定完成不了整体功能了,但起码不能影响其他服务的使用吧
服务降级
服务降级:就是说用户发送请求到A服务接口,由于A服务出现不明故障导致不能及时用户返回正确的结果,这时启动备用方案;
一般来说备用方案,通常是返回一个友好的提示给用户,并不能真正完成用户要做的业务
导致服务降级的几种情况:
- 程序运行异常
- 超时
- 服务熔断调用服务降级
- 线程池打满
总结:服务降级是一种备用方案。
服务熔断
服务熔断:就是说用户发送请求到A服务接口,由于A服务出现不明重大故障导致不能给用户返回正确的结果,直接把A服务熔断掉,直接拒绝用户访问A服务了
这里也说下服务熔断和服务降级的区别:
出现服务降级的情况,可能这时的A服务还是正常的,只不过当前压力比较大,大部分请求能及时响应,部分请求无法及时响应,因此给部分无法及时响应给出备用方案
而服务熔断,是A服务受到了极大的影响,整个服务不可用了,这时直接拒绝用户访问A服务是熔断操作
某个服务发生了服务熔断后,后续一般会先 先调用服务降级,给用户一个友好提示,接着恢复服务的使用。
服务限流
服务限流理解就比较简单点,A服务1秒钟只能处理10个请求,现在同一时间有100个请求调用A服务,A服务肯定承受不了,因此服务限流可以理解为将这100个请求进行排队,不要挤在一块访问A服务
服务降级
Hystrix使用方式1:在controller接口加上@HystrixCommand,并在controller类加上备案方法
降级在生产者服务端
- 在主启动类加上@EnableHystrix
- 给可能会出现高并发或超时的接口加上@HystrixCommand,并编写备案方法,如下:
@GetMapping("/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result: "+result);
return result;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
}
如果调用paymentInfo_TimeOut方法超过3秒或者异常,则会调用paymentInfo_TimeOutHandler方法进行响应。
降级在消费者服务端
- yml加上如下,因为消费者服务使用feign作为服务调用,和hystrix有紧密合作:
feign:
hystrix:
enabled: true
- 在主启动类加上@EnableHystrix
- 示例代码:
@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)
{
String result = paymentHystrixApi.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
总结
上述的写法有两个弊病:
- 备案方法和controller耦合在一起了
- 每个需要降级的方法都得对应一个备案方法,代码冗余
Hystrix使用方式2:在controller接口加上@DefaultProperties,在controller需要降级的方法加上@HystrixCommand
使用方式1的弊病之一:代码会出现冗余。而使用方式2是一定程度上减少了代码冗余,具体使用步骤如下(激活注解和yml步骤省略):
- 在controller类上加上@DefaultPropertie,如下:
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public class OrderHystirxController
{}
- 在@DefaultProperties所在类,需要降级的方法加上@HystrixCommand,如下:
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentHystrixApi.paymentInfo_TimeOut(id);
return result;
}
总结
优点:
- 一定程度上减少了降级方法冗余
- 类里的降级方法,@HystrixCommand如果没有指定具体的降级方法和超时时间,则走默认的@DefaultProperties。如果指定了,则走自己的。
缺点:
- 备案方法和controller耦合在一起了
Hystrix使用方式3:创建一个备案类实现ClientApi接口,重写的方法即为备案方法
话不多说,操作如下(激活注解和yml省略):
- 创建PaymentHystrixApiFallback实现 PaymentHystrixApi,重写的方法即为备案方法:
@Component
public class PaymentHystrixApiFallback implements PaymentHystrixApi {
@Override
public String paymentInfo_OK(@PathVariable("id") Integer id){
return "服务调用失败,提示来自:cloud-consumer-feign-order80";
}
@Override
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return "服务调用失败,提示来自:cloud-consumer-feign-order80";
}
}
- 在指定备案类PaymentHystrixApiFallback,如下:
@Component
@FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentHystrixApiFallback.class)
public interface PaymentHystrixApi {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
假设我们调用paymentInfo_OK方法失败了,就会调用其重写方法返回"服务调用失败,提示来自:cloud-consumer-feign-order80";
总结
优点:
- 将降级方法抽取出来,实现解耦。
2.从根本上解决问题,此方式偏向考虑服务之间调用失败而做出的降级反馈,而方式1和2有很大的不同,方式1和2是针对自身方法,而方式3针对的是服务调用方法
缺点:
- 每个服务接口都有一个备案方法,代码冗余
方式3使用的会比较多一点,但具体情况依项目而定;服务降级的使用多用在消费者服务端
服务熔断
Hystrix服务熔断有3个状态,open,close,half-open
open: 熔断已开启
close:熔断关闭
half-open:熔断半开启
如果服务在正常情况下,一般是close状态,如果由于某种原因出现熔断了,会进行open状态,在休眠一段时间后会进入half-open状态,尝试处理请求,如果处理请求还是失败的,又会回到open状态,反之close。
代码示例实际上和服务降级差不多,但不一样是服务熔断只针对自身方法,如下:
//=========服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
//是否开启熔断机制
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
//某段时间内请求次数阈值
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
//进入open状态,等待多久才进入到half-open状态
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
//达到多少失败百分比进入open状态
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
上面的一些参数可举个例子:最近10次如果失败率达到60%以上,则进入open状态,后面的请求(不管对错)直接返回备案方法。在休眠10秒后进入half-open状态尝试处理请求,如果请求能正常处理,则进入close状态,反之open。