如何利用hystrix实现服务熔断降级
一:需求分析
如果一个服务不可用,例如网络延迟或者流量激增,则会影响依赖于这个服务的其他服务,导致雪崩效应。为了解决这种雪崩效应,使用熔断器Hystrix, 实现服务降级,服务限流的功能,并且为client提供健康的页面状态。
二:解决方案
使用熔断器或者服务降级,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。
fallback和fallbackFactory区别
需要得到导致回退触发的原因需要使用fallbackFactory
配置中心开启Hystrix功能
在application.yml 或者Apollo中开启Hystrix功能
ribbon:
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,默认false
ReadTimeout: 5000 #负载均衡超时时间,默认值5000
ConnectTimeout: 2000 #ribbon请求连接的超时时间,默认值2000
MaxAutoRetries: 0 #对当前实例的重试次数,默认0
MaxAutoRetriesNextServer: 1 #对切换实例的重试次数,默认1
hystrix:
command:
default: #default全局有效,service id指定应用有效
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
enabled: true
isolation:
thread:
timeoutInMilliseconds: 1000 #断路器超时时间,默认1000ms
feign:
hystrix:
enabled: true
- 如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效
- 如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon
- ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效
- ribbon还有MaxAutoRetries对当前实例的重试次数,MaxAutoRetriesNextServer对切换实例的重试次数, 如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作
- 由于ribbon的重试机制,通常熔断的超时时间需要配置的比ReadTimeout长,ReadTimeout比ConnectTimeout长,否则还未重试,就熔断了
- 为了确保重试机制的正常运作,理论上(以实际情况为准)建议hystrix的超时时间为:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout
- (以上太长可不看,如果按ribbon默认的推荐设置看,算出来是10s,但不使用ribbon重试机制的话则为5s,但hystrix默认为1s,因此建议测试环境5s,生产环境机器性能各方面相对较高可设置为3s左右)
添加fallback属性
在接口中的@FeignClient
注解上添加fallback属性来配置指定的处理类。
在热点服务上添加指定fallback类,返回指定内容:
在RPC调用的类上添加指定fallback类,在服务熔断的时候返回fallback类中的内容。
@FeignClient(value = "CIM-BASE-ZUUL", url = "${feign.url.cim-base-zuul}", fallbackFactory = CimBaseZuulServiceFallback.class)
public interface CimBaseZuulService {
/**
* 获取所有设备
*/
@GetMapping("/cim/api-service/find/deviceTypes")
CimBaseZuulResponse<List<DeviceBaseInfoBO>> getAllDeviceInfo();
}
创建回调类
热点服务的回掉类继承FallbackFactory:
创建UgmdpServiceFallback
类继承于FallbackFactory实现回调的方法
@Slf4j
@Component
public class CimBaseZuulServiceFallback implements FallbackFactory<CimBaseZuulService> {
private static final String SYSTEM_CIM = "cim";
private static final String GET_ALL_DEVICE_INFO = "/cim/api-service/find/deviceTypes";
@Override
public CimBaseZuulService create(Throwable e) {
return new CimBaseZuulService() {
@Override
public CimBaseZuulResponse<List<DeviceBaseInfoBO>> getAllDeviceInfo() {
if (e instanceof HystrixTimeoutException) {
throw new TimeoutException(ResponseCode.FEIGN_TIMEOUT, SYSTEM_CIM, GET_ALL_DEVICE_INFO);
} else {
throw new TimeoutException(ResponseCode.DEGRADATION);
}
}
};
}
}
自定义异常
@Data
@NoArgsConstructor
public class TimeoutException extends RuntimeException{
private String code;
private String message;
public TimeoutException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public TimeoutException(ResponseCode exception) {
super(exception.getMsg());
this.code = exception.getCode();
this.message = exception.getMsg();
}
public TimeoutException(ResponseCode exception,String... str) {
super(exception.getMsg());
this.code = exception.getCode();
this.message = String.format(exception.getMsg(),str);
}
}
全局异常捕获
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler({HystrixRuntimeException.class})
@ResponseBody
public RdfaResult<Serializable> timeoutException(HystrixRuntimeException exception) {
Exception cause = (Exception) exception.getFallbackException().getCause().getCause();
log.warn("服务调用超时", cause);
if (cause instanceof TimeoutException){
TimeoutException e = (TimeoutException)cause;
return RdfaResult.fail(e.getCode(), e.getMessage());
}
return RdfaResult.fail(ResponseCode.FAILURE.getCode(), exception.getMessage());
}
}