为什么需要降级和熔断
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。比如单个服务C出现问题,服务B调用服务C就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务B瘫痪。服务与服务之间的依赖性,故障会传播,那么服务A调用服务B也会瘫痪,这样会对整个微服务系统造成灾难性的严重后果,这就会出现服务故障,叫做“雪崩”效应。
而降级或熔断的目的就是防止雪崩,停止服务之间的级联故障并提供后备选项,来提高系统的整体弹性。
当存在第三方访问调用时,这些问题会更加严重。“第三方”是一个隐藏了实施细节的“黑匣子”,可以随时更改,并且每个客户端库的网络或资源配置都不同,通常难以监控和更改,这时降级就显得更为重要了。
当然,降级也可以使用在服务内部,来实现容错或保证程序的主要功能的可用性。
降级和熔断的区别
降级:降级就是在主流程、主方案以外,还有planB,当主方案出现问题时,可以马上切换到planB。降级也分为自动降级和手动降级,前者是系统自动检测到问题时自动切换,后者是系统检测到问题报警,人为的切换,降级代表着系统相比降级之前其功能表现不如之前的完美。
降级往往是一个兜底方案,需要在做设计的时候结合业务场景考虑哪些环节可能会出问题,出了问题如何降级,是自动降级还是手动降级,降级后需要启用怎么样的应急处理流程等等。
一般以下情況均可采用降級方法:超时降级、异常降级、失败次数降级、拒绝服务降级、限流也是降级。
熔断:熔断一般是在服务异常或调用出现问题时,及时断掉,不再调用。就像保险丝,当达到一定电流时,保险丝熔断,保护其他电器不受损。除了服务调用异常可以熔断外,还有一种情况是,在秒杀或大促时,可以熔断一些边缘服务,从而保证下单等主要服务的可用性。
需要采用熔断的情况一般是:涉及到核心功能运行时,熔断边缘服务、服务调用异常时,配合降级方法planB,进行熔断、服务被攻击时熔断等等。
不管是降级还是熔断,都是为了保证了系统的稳定性,可用性。
Hystrix实现
Hystrix
可通过Hystrix实现,Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。
Hystrix通过隔离服务之间的访问点(这一点理解的不是很深,欢迎指点),停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
Hystrix之前写过一篇博文,也一直在完善,大家可粗略参考:【Spring Cloud】-Hystrix断路器
自定义实现
我们也可以根据超时降级或信号量限流降级等思路,自己实现。
可容错的超时降级
@GetMapping(value = "/createOrder3")
public String createOrder3(String message) throws Exception {
Future<String> future = executorService.submit(() -> {
//如果随机时间大于100,那么出发容错
int value = random.nextInt(200);
System.out.println("createOrder2 cost" + value + "ms");
Thread.sleep(value);
String returnValue = "createOrder2:" + message;
return returnValue;
});
//future.get 方法,强制执行,如果超过100毫秒,就报TimeoutException
String retureValue = "";
try {
//容错,如果超时异常,执行planB
retureValue = future.get(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
retureValue = errorContent(message);
}
return retureValue;
}
//planB,降级方法
public String errorContent(String message){
return "Falut";
}
可容错的超时降级-增强版1,通过拦截器实现
@GetMapping(value = "/createOrder4)
public String createOrder4(String message) throws Exception {
Future<String> future = executorService.submit(() -> {
//如果随机时间大于100,那么出发容错
int value = random.nextInt(200);
System.out.println("createOrder2 cost" + value + "ms");
Thread.sleep(value);
String returnValue = "createOrder2:" + message;
return returnValue;
});
return future.get(100, TimeUnit.MILLISECONDS);
}
//通过ExceptionHandler,拦截TimeoutException
@RestControllerAdvice(assignableTypes = customize.class)
public class CircuitBreakerControllerAdvice {
@ExceptionHandler
public void onTimeoutException(TimeoutException timeoutException, Writer writer) throws IOException {
writer.write(errorContent(""));
}
//planB,降级方法
public String errorContent(String message){
return "Fault";
}
}
可容错的超时降级-增强版2,通过切面实现
@TimeoutCircuitBreaker(timeout = 100)
@GetMapping(value = "/createOrder6)
public String createOrder6(String message) throws Exception {
//如果随机时间大于100,那么出发容错
int value = random.nextInt(200);
System.out.println("createOrder2 cost" + value + "ms");
Thread.sleep(value);
String returnValue = "createOrder2:" + message;
return returnValue;
}
//注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeoutCircuitBreaker {
long timeout();
}
//切超时注解
/**
* 切注解--超时
* @param point
* @param message
* @param cricuitBreaker
* @return
* @throws Throwable
*/
@Around("execution(* com.java8.local_date.degradationAndFusing.customize.customize.createOrder6(..)) && args(message) && @annotation(cricuitBreaker)")
public Object CreateOrder6Timeout(ProceedingJoinPoint point, String message, TimeoutCircuitBreaker cricuitBreaker) throws Throwable{
long timeout = cricuitBreaker.timeout();
return doInvoke(point,message,timeout);
}
private Object doInvoke(ProceedingJoinPoint point, String message, long timeout) throws Throwable{
Future<Object> future = executorService.submit(() -> {
Object returnValue = null;
try {
returnValue = point.proceed(new Object[]{message});
} catch (Throwable throwable) {
}
return returnValue;
});
Object returnValue = null;
//future.get 方法,强制执行,如果超过100毫秒,就报TimeoutException
try {
returnValue = future.get(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
returnValue = errorContent("");
}
return returnValue;
}