一、限流的方式
1.计数器(counter)
单位时间内限制请求总数
2.滑动窗口(sliding window)
计数器的升级版,动态的单位时间限定(详情参见TCP滑动窗口协议)
3.漏桶(leaky bucket)
漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;漏桶限制的是常量流出速率,从而平滑突发流入速率;
4.令牌桶(token bucket)
令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;令牌桶限制的是平均流入速率,并允许一定程度突发流量;
二、优缺点对比
计数器简单,允许突发请求,无法控制单位时间内热点请求问题与相邻单位时间头尾过热问题
滑动窗口相对复杂,允许突发请求,可以控制相邻单位时间头尾过热问题,仍存在单位时间内热点请求问题(将单位时间拆分的越精细对头尾过热问题控制越好)
漏桶流量固定,不允许突发请求,不存在单位时间内热点请求问题与相邻单位时间头尾过热问题
令牌桶,允许突发请求,可以控制相邻单位时间头尾过热问题,仍存无法完全控制单位时间内热点请求问题
三、手动实现
1.计数器
1.1)简单计数器
AomicInteger atomicInteger = new AomicInteger();
Integer currentTotal = null;
try{
currentTotal = atomicInteger.incrementAndGet();
if (currentTotal > 50) {
// return
} else {
// do something
}
} finally {
if (null != currentTotal) {
atomicInteger.decrementAndGet();
}
}
1.2)分布式计数器
/**
* 指定单位时间(s)内数量限制是否需要限制
*
* @param key 限制标识
* @param unitTime 单位时间
* @param limitQuantity 限制数量
* @return
*/
public Boolean checkNeedLimiter(String key, Long unitTime, Long limitQuantity) {
Long count = redisTemplate.opsForValue().increment(key, 1);
// 首次统计,设置超时时间
if (1 == count) {
redisTemplate.expire(key, unitTime, TimeUnit.SECONDS);
}
// 单位时间内数量大于限定数量 返回超限
if (count > limitQuantity) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
2.滑动窗口
3.漏桶
4.令牌桶
四、RateLimiter
com.google.common.util.concurrent.RateLimiter
// SmoothRateLimiter 1s 内生成5个令牌 200ms 生成一个令牌
static final RateLimiter rateLimiter = RateLimiter.create(5);
public static void main(String[] args) throws InterruptedException {
int totalInt = 0;
for (int i = 0; i < 10; i++) {
int randomInt = (int) (Math.random() * 10) * 10;
totalInt += randomInt;
System.out.println("执行时间:" + totalInt + "ms");
Thread.sleep(randomInt);
boolean acquire = rateLimiter.tryAcquire();
System.out.println("是否获得令牌:" + acquire);
}
}
运行结果
执行时间:50ms
是否获得令牌:true 首次
执行时间:130ms
是否获得令牌:false
执行时间:190ms
是否获得令牌:false
执行时间:200ms 第一个200ms一个
是否获得令牌:true
执行时间:260ms
是否获得令牌:false
执行时间:330ms
是否获得令牌:false
执行时间:410ms 第二个200ms一个
是否获得令牌:true
执行时间:440ms
是否获得令牌:false
执行时间:510ms
是否获得令牌:false
执行时间:520ms
是否获得令牌:false