- 最容易想到的方案,每次访问的时候比较时间,超过区间,就重置;没超过就比较count 和limit;
- 上述方法会出现不均匀问题,造成短时间达到2倍limit。比如1:59 和2:01;
- 使用 bucketToken的思路,均匀的投放令牌,guva提供了一个非常好的思路,不必使用 timer 真的投放,而是在获取token的时候,查看时间,看已经积攒了多少个token;
- 分布式的问题,可以使用redis来解决;为了防止race condition,可以使用lua 脚本;
package com;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class RateLimitSimple {
Map<String,RateLimitor> cachedLimitors = new HashMap<>();
Map<String,BucketToken> cachedBuckets = new HashMap<>();
private long intervalInMiss = 1000* 60;
private long capLimit =1000;
/// 存在的问题:
/// 20:59.59 访问1000; 20:60:01 访问1000,有不均匀的问题;可能瞬间造成很大的2倍的流量;
public boolean canAccess(String key){
long currentMills = System.currentTimeMillis();
RateLimitor rateLimitor = cachedLimitors.get(key);
if(Objects.isNull(rateLimitor)){
cachedLimitors.put(key, new RateLimitor(currentMills,capLimit));
return true;
}else{
long diff = currentMills - rateLimitor.startTimer;
if(diff > intervalInMiss){
rateLimitor.startTimer =currentMills;
rateLimitor.cap = capLimit;
cachedLimitors.put(key,rateLimitor);
return true;
}else{
if(rateLimitor.cap >capLimit){
return false;
}else{
rateLimitor.cap = rateLimitor.cap+1;
cachedLimitors.put(key,rateLimitor);
return true;
}
}
}
}
/// 使用 token bucket,文件问题是如何均匀的往筒里面放入token。guvaa里面有非常经典的方法
/// 不用timer,而是在取token的时候,按照时间戳,计算一下,过去放入了多少个token;
public boolean canAccessUsingBucket(String key){
long currentMills = System.currentTimeMillis();
BucketToken bucketToken = cachedBuckets.get(key);
if(Objects.isNull(bucketToken)){
cachedBuckets.put(key, new BucketToken(currentMills,capLimit));
return true;
}else{
long diff = currentMills - bucketToken.lastFilledTimer;
long remaining = 0;
if(diff > intervalInMiss){
remaining = capLimit;
}else{
long grantedToken = diff/intervalInMiss * capLimit;
remaining = Math.min(grantedToken+bucketToken.remainingToken, capLimit);
}
bucketToken.lastFilledTimer = currentMills;
if(remaining == 0){
bucketToken.remainingToken = 0;
return false;
}else{
bucketToken.remainingToken = remaining-1;
return true;
}
}
}
public boolean canAccessUsingRedis(String key){
/// 对于上面两种,都可以使用redis来实现,第一种方法,无法规避不均匀的问题;
/// 第二种方法,可以把 bucketTOken 存在 redis中;
/// 为了规避 多线程的问题, 需要使用lua 脚本来规避,这样就比较ok了
return true;
}
class RateLimitor{
long startTimer;
long cap;
public RateLimitor(long startTimer, long cap){
this.startTimer = startTimer;
this.cap = cap;
}
}
class BucketToken{
long lastFilledTimer;
long remainingToken;
public BucketToken(long lastFilledTimer, long remainingToken){
this.lastFilledTimer = lastFilledTimer;
this.remainingToken = remainingToken;
}
}
}
网友评论