Hystrix服务降级fallback
通过前面章节的讲解,我们都知道当服务熔断被触发之后,我们再次访问会返回如下结果:
这样的响应结果,提供给用户显然是不够友好的。上面的提示信息有两层含义:
- 服务熔断被触发,也就是断路器处于开启状态
- 断路器被触发之后,访问fallback方法,但是这个fallback方法我们之前没有定义
当服务提供者故障触发熔断机制,此时你需要预先提供一个处理方法,作为降级后的执行方法一般叫fallback,fallback方法返回值一般是设置的默认值或者来自缓存,或者是一些友好提示信息。
一、服务降级发生的条件?
满足下列条件任何一个异常条件,都会产生服务降级
- 被访问的服务接口达到熔断标准(SHORT_CIRCUITED)
- 被访问的服务接口代码抛出异常(FAILURE)
- 被访问的服务接口响应超时(TimeOut)
- hystrix线程池或信号量爆满(THREAD_POOL_REJECTED 或SEMAPHORE_REJECTED )
Failure Type | Exception class | Exception.cause | subject to fallback |
---|---|---|---|
FAILURE | HystrixRuntimeException |
underlying exception (user-controlled) | YES |
TIMEOUT | HystrixRuntimeException |
j.u.c.TimeoutException |
YES |
SHORT_CIRCUITED | HystrixRuntimeException |
j.l.RuntimeException |
YES |
THREAD_POOL_REJECTED | HystrixRuntimeException |
j.u.c.RejectedExecutionException |
YES |
SEMAPHORE_REJECTED | HystrixRuntimeException |
j.l.RuntimeException |
YES |
所以我们可以认为:服务降级实际上也是“异常处理”的一种方式,处理的是上面的5种异常。
二、在控制层实现服务降级(方法级别)
在上一节服务熔断的代码的基础上加上服务降级方法配置
- 在
@HystrixCommand
注解加上属性fallbackMethod属性- 当捕获到任何一种服务降级的异常类型的时候,表示原函数(pwdreset)已经无法正确响应结果,执行fallback函数(pwdresetFallback)。
- 增加fallbackMethod对应的函数,返回值要与原函数一致。
**(重要)**为什么在控制层实现服务降级?
- 在实际的生产代码中,一种比较好的异常处理机制是:将服务层、持久层代码等所有底层代码抛出的异常转换为自定义异常不断的向上抛出,最后由控制层处理或者由Spring 全局异常处理。从而避免异常在底层被处理,上层无感应。可能造成一种现象:用户进行了一个操作,操作界面没有任何反应,但是后台服务报错了。
- 服务降级也是通过异常抛出及捕获实现的,所以一般不要在服务层和持久层等底层服务方法上进行hystrix配置。相当于把异常在底层处理了,造成上层无感应。
此时在服务熔断之后(或者其他的服务降级条件满足之后),我们再次访问“/sysuser/pwd/reset”接口。结果如下,说明执行了本地fallback方法。
三、Hystrix类级别的配置(笔者推荐)
第二小节中这种方法级别的服务降级配置方式的缺点十分的明显:那就是我们需要针对方法级别进行服务熔断和服务降级的配置;不只是配置,我们还需要针对每一个方法写fallback方法,无疑很大程度上增加了我们的代码量。那么有没有一种可以全局实现服务降级的配置方式呢?就是下面要为大家介绍的:
@DefaultProperties(defaultFallback = "commonFallbackMethod")
@DefaultProperties
是一个类方法级别的注解- defaultFallback 可以指定该类中所有方法在发生服务降级的时候,执行的本地fallback函数。
- 需要我们在一个类中定义一个fallback函数,如:commonFallbackMethod
public AjaxResponse commonFallbackMethod() {
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR,
"系统繁忙,请稍后再试!");
}
最后在需要进行服务降级后执行fallback的方法的方法上面加上
@HystrixCommand
这样我们实现服务降级的代码就减少了很多,但是仍然存在一个问题让开发者不爽:我们需要在每一个类里面写一个commonFallbackMethod函数,为了降低fallback函数与实际Controller业务处理类的耦合,进一步减少代码的冗余,我们通常是可以定义一个BaseController,然后让其他的Controller类来继承。
public class BaseController { //通用hystrix回退方法 public AjaxResponse commonFallbackMethod() { return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR, "系统繁忙,请稍后再试!"); } }
Hystrix结合Feign服务降级
通过前面几个小节的说明,对于服务降级目前有两种方式:
- 使用DefaultProperties注解在类级别的代码上进行服务降级,这种方法一定程度上减少了很多冗余代码。但是通用fallback方法仍然与实际业务的处理方法耦合在一个类中,可以通过BaseController的方式解决。笔者较为推荐这种方式
- 使用HystrixCommand注解的CommandProperties配置,在方法级别实现服务降级。这种方法代码十分冗余,需要针对每一个方法做配置,写fallback。最好用于一些重点业务的个性化接口。
- 用一句话总结就是:追求统一处理、允许个性化实现
下面为大家介绍服务降级的另一类方法:在FeignClient上实现服务降级。为什么我称它是另一类方法,而不是另一种方法?因为FeignClient上实现服务降级与上面两种方法的思考的角度是不同的:
- FeignClient上实现服务降级,从服务调用者的角度考虑:如果服务提供者出现连接超时、服务宕机等问题,作为服务调用者我该如何快速的对服务提供者的接口进行降级,避免造成服务调用者自己的崩溃。
- HystrixCommand实现服务降级,从服务提供者角度考虑:如果有服务调用者调用我的服务,并且我自己的代码或者触发熔断降级规则后,我该如何快速的告知服务调用者,避免造成服务调用者崩溃。
一、在FeignClient上实现服务降级
- 首先还是要将Hystrix集成到Spring Cloud服务中,参考《Hystrix集成并实现服务熔断》得第三小节:微服务集成Hystrix
- 在服务配置文件中打开feign结合hystrix的开关
feign:
hystrix:
enabled: true
在FeignClient注解增加fallback处理实现类,如:SmsServiceFallback。
@FeignClient(name="ASERVICE-SMS",fallback = SmsServiceFallback.class)
public interface SmsService {
@PostMapping(value = "/sms/send")
AjaxResponse send(@RequestParam("phoneNo") String phoneNo,
@RequestParam("content") String content);
}
书写SmsServiceFallback代码,该类要实现FeignClient注解的接口函数。当使用Feign客户端远程调用SmsService .send方法,如果远程服务不可达(网络不可达或宕机),就会执行SmsServiceFallback.send方法作为fallback。
@Component
public class SmsServiceFallback implements SmsService {
@Override
public AjaxResponse send(String phoneNo, String content) {
return AjaxResponse.error(CustomExceptionType.SYSTEM_ERROR
,"短信发送接口失败!");
}
}
- 优点:将fallback服务降级方法与实际的业务处理方法分离,耦合度降低,从这个角度来说对程序员比较友好。
- 缺点:FeignClient注解的接口有多个方法,实现类就要写多个fallback,所以代码冗余量仍然非常大。这个缺点在我看来似乎无关紧要了,因为接口函数定义实际根本就不用我们写,通过IDE一个回车就可以搞定。接口函数的实现内容通过提取公共代码方式就可以搞定。虽然代码行数可能仍然较多,但是独立整齐规范。