令牌桶算法
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
使用guava实现限流
pom依赖
多加如下依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
yml 去掉数据库配置
AbstractInterceptor
public abstract class AbstractInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return preHandle(request);
}
protected abstract boolean preHandle(HttpServletRequest request);
}
RateLimitInterceptor
@Component
public class RateLimitInterceptor extends AbstractInterceptor {
public static final int REQUEST_COUNT = 1;
/*** set the number of requests per second */
private static final RateLimiter rateLimiter = RateLimiter.create(REQUEST_COUNT);
@Override
protected boolean preHandle(HttpServletRequest request) {
if (!rateLimiter.tryAcquire()) {
System.out.println(">>>>>>>>>> 亲!请稍后重试!");
return false;
}
System.out.println(">>>>>>>> 恭喜您下单成功!");
return true;
}
}
WebMvcConfig
/**
* @author administrator
* extends WebMvcConfigurationSupport or implements WebMvcConfigurer
*/
@Component
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
}
}
DemoTestController
@RestController
public class DemoTestController {
public static final String SUCCESS = "ok";
@RequestMapping("/demoTest")
public String demoTest() {
return SUCCESS;
}
}
jmeter测试
测试效果
本人执行了三次
guava基本方法详解
以下许可的意思为token即令牌的意思
double acquire() 从RateLimiter获取一个许可,该方法会被阻塞直到获取到请求,返回0表示获取令牌成功,否则阻塞等待获取
double acquire(int permits)从RateLimiter获取指定许可数,该方法会被阻塞直到获取到请求
static RateLimiter create(double permitsPerSecond)根据指定的稳定吞吐率创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少查询)
static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)根据指定的稳定吞吐率和预热期来创建RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
double getRate()返回RateLimiter 配置中的稳定速率,该速率单位是每秒多少许可数
void setRate(double permitsPerSecond)更新RateLimite的稳定速率,参数permitsPerSecond 由构造RateLimiter的工厂方法提供。
String toString()返回对象的字符表现形式
boolean tryAcquire()从RateLimiter 获取许可,如果该许可可以在无延迟下的情况下立即获取得到的话,返回true表示获取成功,否则等待获取
boolean tryAcquire(int permits)从RateLimiter 获取许可数,如果该许可数可以在无延迟下的情况下立即获取得到的话
boolean tryAcquire(int permits, long timeout, TimeUnit unit)从RateLimiter 获取指定许可数如果该许可数可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可数的话,那么立即返回false (无需等待)
boolean tryAcquire(long timeout, TimeUnit unit)从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)
其他限流解决方案
1、redis incr加过期时间来限流。
int current = jedis.incr(key);
if (current + 1 > limit) //如果超出限流大小
return 0;
else if (current == 1) //只有第一次访问需要设置2秒的过期时间
jedis.expire(key, “2”);
return 1
2、另外还有通过redis+lua来实现限流。
3、hystrix的线程池就类似漏桶的思路。