Guava限流
为什么要做限流
通常我们的应用在部署之前都会先进行评估,有多少的调用量,需要多少台机器,能承受多大的流量;但是难免会有流量突然增大的时候,比如某时段某个接口突然遭受攻击,这时候某些机器可能会承受不了这个压力,导致崩溃,从而导致整个系统不可用。在我们的系统中, 会设置一定的阈值,保护我们的系统能正常运行。
通常我们流量控制的策略有:限流、降级、分流
原理
常用的限流算法有两种:令牌桶算法和漏桶算法。
漏桶算法
有一个漏桶,按照固定的出水速率漏水,进水的速率是不确定的,如果桶中的水满了,就会溢出(拒绝请求)。因为桶的容量(能承受的最大流量)是固定的,所以如果进水的速率大于出水的速率,一定时间后,就会导致直接溢出。
漏桶有两个变量,一个是水桶的大小,另一个是漏洞的大小。
缺点:因为漏桶出水的速率是固定的,所以无论流量是大还是小,出水速率都是不变的,这就缺乏效率了。
令牌桶算法
相比于漏桶算法,令牌桶算法在流量突然激增时,处理请求效率高得多。
有一个令牌桶,每次按照固定速率往里面加令牌,来一个请求,则从令牌桶中拿一个令牌,如果令牌桶中没有令牌,则拒绝请求。
当桶满的时候,新加的令牌就会被丢弃。
如果流量增大的话,我们可以控制添加令牌的速率,这样的话处理请求的效率就上去了,这就是相比漏桶算法更好的地方。
实战
例子主要是通过guava的rateLimiter来做限流:
增加一个注解:
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAnnotation {
}
对该注解增加一个切面:
@Aspect
@Component
public class RateLimitAop {
//限流每秒1次。如果设置为n,则每秒限流为1/n
private RateLimiter rateLimiter= RateLimiter.create(1);
@Pointcut(value = "@annotation(cn.tongdun.fp.admin.annotation.RateLimitAnnotation)")
public void rateLimit(){
}
@Around("rateLimit()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Boolean flag = rateLimiter.tryAcquire();
Object obj = null;
try {
if (flag) {
obj = joinPoint.proceed();
}else{
throw new Exception("限流");
}
} catch (Throwable e) {
throw e;
}
return obj;
}
}
使用了该注解的接口,会被拦截:
@RateLimitAnnotation
public void test(Student student) {
...
}