一、 限流算法介绍
- 漏桶算法
该算法关键是漏桶取出请求的速率是固定的,请求过多(流入)超过容量时会排队或者拒绝。所以处理的请求的速度是固定的,面对大量的突发请求,请求会排队按照固定的速率处理。
- 令牌桶算法
该算法的核心是在创建令牌桶的时候,指定固定的速率生成令牌可以应对突发流量。同时能够使用所有的请求平均分布到时间段区间内。
和漏桶不一样,令牌桶有以下特点:
- 固定的速率放入令牌
- 正常情况下,桶内会保持一定数量的令牌(桶大小),满则丢弃令牌。当请求到达时取令牌,取到则可以执行。
- 桶空了取不到则等待或直接丢弃(尝试获取返回false,可以自定义处理)
二、 令牌桶算法实现-guava RateLimiter
他山之石可以攻玉:令牌桶在限流框架中广泛应用,guava工具包中RateLimiter提供令牌桶算法的实现。
支持两种方式:
- 创建一个应对突发流量的令牌桶SmoothBursty
public static RateLimiter create(double permitsPerSecond)
- 创建一个可以预热的令牌桶SmoothWarmingUp,可以指定预热时间
public static RateLimiter create(double permitsPerSecond, long
warmupPeriod, TimeUnit unit)
调用的详细API了可以参考:http://ifeve.com/guava-ratelimiter/
springboot应用中代码示例
package shen.example.limit;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RateLimiterController {
//设置令牌桶的生成令牌的速率 1qps
RateLimiter rateLimiter = RateLimiter.create(1);
@GetMapping("/index")
public String doGet(){
if(rateLimiter.tryAcquire()){
return "success: 请求正确处理";
}else {
return "error:系统操作太频繁,请重试";
}
}
}
发送请求频率低的情况:
发送请求频率高的情况:
我们可以根据不同的资源设置不同RateLimiter,令牌生成频率设置不一样。
public static Map<String,RateLimiter> resourceRateLimiters = new ConcurrentHashMap<>();
三、 限流框架封装
使用AOP+注解很容易实现封装,实现低侵入性的限流框架。
- maven需要引入的依赖‘
’
aop和guava依赖:
<!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.2.6.RELEASE</version> </dependency> <!--guava--> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency>
- 定义注解MyRateLimiter
package shen.example.limit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 基于令牌桶实现的限流器
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRateLimiter {
/**
* 资源id
* @return
*/
String resourceId() default "";
/**
* 限制请求频率
* 即令牌生成频率
* @return
*/
double qps() default 1.0d;
/**
* 预热时间
* 不大于0时采用 SmoothBursty
* 大于0时 采用 moothWarmingUp
* @return
*/
long warmupPeriod() default 0L;
/**
* 预热时间单位
* warmupPeriod > 0
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
- aop定义切面和处理注解
package shen.example.limit.aop;
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import shen.example.limit.MyRateLimiter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@Aspect
public class RateLimiterAspect {
public Map<String, RateLimiter> resourceRateLimiters = new ConcurrentHashMap<>();
/**
* aop round 处理注解MyRateLimiter
* @param pjp
* @param myRateLimiter
*/
@Around("@annotation(myRateLimiter)")
public Object rateLimiterRound(ProceedingJoinPoint pjp, MyRateLimiter myRateLimiter) throws Throwable {
//限流处理
long warmupPeriod = myRateLimiter.warmupPeriod();
double qps = myRateLimiter.qps();
RateLimiter rateLimiter = null;
//SmoothBursty
if(warmupPeriod <= 0 ){
rateLimiter = RateLimiter.create(qps);
//SmoothWarmingUp
} else {
rateLimiter = RateLimiter.create(qps,warmupPeriod,myRateLimiter.timeUnit());
}
//获取令牌桶
RateLimiter limiter = resourceRateLimiters.putIfAbsent(myRateLimiter.resourceId(),rateLimiter);
if(limiter == null ){
limiter = rateLimiter;
}
//尝试获取令牌
if(limiter.tryAcquire()){
return pjp.proceed();
}else {
return "error:请求太频繁,稍后再试";
}
}
}
- 业务代码中使用注解实现限流功能
直接使用注解即可实现功能,指定资源id/qps/令牌桶预热
package shen.example.limit;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RateLimiterController {
//设置令牌桶的生成令牌的速率 1qps
RateLimiter rateLimiter = RateLimiter.create(1);
@MyRateLimiter(resourceId = "index",qps = 2.0d)//接口限流注解
@GetMapping("/index")
public String doGet(){
//do something
return "success: 请求正确处理";
}
}
同样使用上面的限流功能,而且可复用。
四、 总结
本文简单介绍了目前主要的限流算法:漏桶和令牌桶算法,在不同场景使用。然后使用guava实现的令牌桶实现了一个简单限流例子。最后利用AOP+注解进行封装实现低侵入性限流框架。希望以上内容对需要了解限流和进行限流开发的朋友有帮助,以后有机会再详细介绍分布式限流。