【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级,手写实现一个微服务熔断限流器
服务雪崩
服务雪崩时分布式系统中会遇到的一个问题,由于分布式系统存在服务间调用的关系,一个服务不可用,往往会影响到调用该服务的另外一个服务。
在分布式系统中,一个服务由于程序bug或流量过大导致不可用,进而导致调用该服务的另一个服务也不可用,然后这种不可用继续影响上游其他服务导致其不可用,这种不可用的情况不断放大的过程,就是服务雪崩。
而应对这种服务雪崩问题的解决方案就是熔断、限流、降级。
熔断、限流、降级
熔断
熔断的作用类似于电路中的保险丝。电路中正确安置保险丝,保险丝就会在电流异常升高到一定的高度和热度的时候,自身熔断切断电流,保护了电路安全运行。
熔断通过增加一个断路器实现。
断路器一般有三个状态:关闭、打开、半开。
- 关闭状态:如同一个开关处于闭合状态,此时接口可以执行正常的逻辑处理。
- 打开状态:如果一个开关处于打开状态,此时接口的所有请求都会被降级处理。
- 半开状态:处于半开状态的断路器,允许一个请求执行正常的逻辑处理,这相当于是试探性的调用一下,如果能正常返回结果,那么断路器恢复为闭合状态,否则变为打开状态。
断路器的三种状态间的转换如下图:
当断路器处于打开状态时,由于接口的正常逻辑无法执行,此时就是进行降级处理。
降级
降级是指原有的逻辑无法正常执行,转而执行的一段备用的有损的逻辑。
比如服务A的接口调用服务B的接口发生异常或超时,那么服务A的接口进行降级逻辑,调用本地的一个方法,返回默认的数据。
除此以外,断路器打开,那么也会执行降级逻辑,而不会走正常逻辑。
限流
限流也叫流控,也就是流量控制,作用是限制流入服务接口的流量大小。每一个服务或接口能扛住的流量大小都是有上限的,如果超过了这个上限,就有可能造成性能下降或服务不可用。
限流通过在服务或接口前添加限流器实现。如果流量没有超过限流器的阈值,那么请求可以正常通过;如果流量超过限流器的阈值,那么超过阈值的这一部分请求就要被拦截掉。
被流控的请求,可以报错返回错误信息,也可以走降级处理的逻辑。
限流器的限流算法包括:
- 简单时间窗算法
- 滑动时间窗算法
- 漏桶算法
- 令牌桶算法
这些算法在下面会用代码进行解析,这里先跳过。
手写实现一个微服务熔断限流器
架构设计
我们设计的熔断限流框架包括的组件有:
- FlowLimiter:限流器
- Breaker:断路器
- Fallback:降级逻辑
当我们的系统接进来一个请求时,先经过FlowLimiter(限流器)处理。如果FlowLimiter的当前流量还没有达到限流阈值,请求正常往下走;如果FlowLimiter的当前流量已经达到了限流阈值,那么就转而走Fallback(降级逻辑)进行降级处理。
如果一个请求成功通过了FlowLimiter后,就会到Breaker(断路器)的处理。断路器判断自身当前状态,如果是关闭状态或半开状态,请求都可以正常通过;如果断路器状态是打开状态,那么请求走Fallback(降级逻辑)进行降级处理。
如果一个请求成功通过了Breaker后,就会执行目标方法。如果目标方法正常执行,那么返回正常处理结果;如果目标方法执行异常或超时,那么请求走Fallback(降级逻辑)进行降级处理。
总体上,我们的框架是通过AOP给目标方法生成动态代理对象,在AOP的增强逻辑中实现的熔断、限流、降级等功能。
代码实现
整体逻辑
首先我们定义一个注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD})
public @interface Protected {
...
}
@Protected注解用于修饰指定接口或方法,表示这个接口或方法已经被我们的框架保护,具有熔断、限流、降级等效果。
然后需要定义一个切面类。
/**
* @author huangjunyi
* @date 2023/12/19 19:24
* @desc
*/
@Aspect
public class ProtectorAspect implements PriorityOrdered {
...
@Pointcut("@annotation(com.huangjunyi1993.simple.microservice.protector.aop.Protected)")
public void pointCut() {
}
@Around(value = "pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
...
}
...
}
我们通过SpringAOP扫描被@Protected注解修饰的方法,给它们生成代理对象,并且通过SpringAOP的@Around注解定义了环绕增强逻辑,当请求被@Protected注解修饰的接口方法接收时,就会进入这里的增强逻辑,增强逻辑做的自然就是熔断、限流、降级等这些事情。
而使用的时候,只要在对应接口方法上添加@Protected注解,并指定对应的属性即可(@Protected注解中的各属性会在后面讲解)
@GetMapping("/flow/simple")
@Protected(name = "testFlowSimple", enableFlowLimiter = true, flowLimiterName = FlowLimiterNameConstant.SIMPLE_TIME_WINDOW, limit = 2) // 测试限流效果
public Map<String, Object> testFlowSimple() {
...
}
ProtectorAspect#aroundMethod(ProceedingJoinPoint)整体流程:
@Around(value = "pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
// 1、获取接口对应的类名、方法名、属性类型
...
// 2、如果接口启用了限流器,调用限流器进行验证是否需要限流
if (!annotation.enableFlowLimiter() || checkCanPass(annotation, name)) {
...
// 3、如果接口启用了断路器,检查断路器整体决定是否继续往下执行
// 如果断路器打开,执行fallback逻辑
if (annotation.enableBreaker()) {
...
}
// 4、执行目标方法,并记录断路器(成功/失败),
// 如果超时或异常,执行fallback逻辑
}
// 被流控了,执行fallback逻辑
}
下面就对ProtectorAspect#aroundMethod(ProceedingJoinPoint)方法中的具体实现进行讲解。
ProtectorAspect#aroundMethod(ProceedingJoinPoint)具体实现
1、获取接口对应的类名、方法名、属性类型
@Around(value = "pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
// 1、获取接口对应的类名、方法名、属性类型
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 类名
String className = methodSignature.getDeclaringTypeName();
// 方法名
String methodName = methodSignature.getMethod().getName();
// 参数类型列表
Class[] parameterTypes = methodSignature.