外观
分布式限流器具体怎么实现的
令牌桶算法(Token Bucket)
设计思路: 以固定速率向桶中添加令牌,请求需要消耗令牌,无令牌时拒绝请求。
import redis.clients.jedis.Jedis;
public class TokenBucketRateLimiter {
private static final String REDIS_KEY = "token_bucket_rate_limiter";
private final Jedis jedis;
private final int capacity;
private final double rate;
private static final String LUA_SCRIPT =
"local key = KEYS[1] " +
"local tokensNeeded = tonumber(ARGV[1]) " +
"local capacity = tonumber(ARGV[2]) " +
"local rate = tonumber(ARGV[3]) " +
"local now = tonumber(ARGV[4]) " +
"local lastUpdate = tonumber(redis.call('hget', key, 'last_update')) " +
"local tokens = tonumber(redis.call('hget', key, 'tokens')) " +
"local elapsedTime = now - lastUpdate " +
"local newTokens = math.min(capacity, tokens + elapsedTime * rate) " +
"if newTokens >= tokensNeeded then " +
" redis.call('hset', key, 'tokens', newTokens - tokensNeeded) " +
" redis.call('hset', key, 'last_update', now) " +
" return 1 " +
"else " +
" redis.call('hset', key, 'tokens', newTokens) " +
" redis.call('hset', key, 'last_update', now) " +
" return 0 " +
"end";
public TokenBucketRateLimiter(String host, int port, int capacity, double rate) {
this.jedis = new Jedis(host, port);
this.capacity = capacity;
this.rate = rate;
initialize();
}
private void initialize() {
jedis.hset(REDIS_KEY, "tokens", String.valueOf(capacity));
jedis.hset(REDIS_KEY, "last_update", String.valueOf(System.currentTimeMillis() / 1000));
}
public boolean allowRequest(int tokensNeeded) {
Object result = jedis.eval(LUA_SCRIPT, 1, REDIS_KEY, String.valueOf(tokensNeeded),
String.valueOf(capacity), String.valueOf(rate), String.valueOf(System.currentTimeMillis() / 1000));
return (Long) result == 1;
}
public void close() {
jedis.close();
}
}
漏桶算法(Leaky Bucket)
设计思路: 请求以固定速率被处理,超出桶容量的请求被丢弃或排队。
import redis.clients.jedis.Jedis;
public class LeakyBucketRateLimiter {
private static final String REDIS_KEY = "leaky_bucket_rate_limiter";
private final Jedis jedis;
private final int capacity;
private final double rate;
private static final String LUA_SCRIPT =
"local key = KEYS[1] " +
"local now = tonumber(ARGV[1]) " +
"local capacity = tonumber(ARGV[2]) " +
"local rate = tonumber(ARGV[3]) " +
"local lastUpdate = tonumber(redis.call('hget', key, 'last_update')) " +
"local water = tonumber(redis.call('hget', key, 'water')) " +
"local elapsedTime = now - lastUpdate " +
"local leakedWater = math.min(water, elapsedTime * rate) " +
"local newWater = math.max(0, water - leakedWater) " +
"if newWater + 1 <= capacity then " +
" redis.call('hset', key, 'water', newWater + 1) " +
" redis.call('hset', key, 'last_update', now) " +
" return 1 " +
"else " +
" redis.call('hset', key, 'water', newWater) " +
" redis.call('hset', key, 'last_update', now) " +
" return 0 " +
"end";
public LeakyBucketRateLimiter(String host, int port, int capacity, double rate) {
this.jedis = new Jedis(host, port);
this.capacity = capacity;
this.rate = rate;
initialize();
}
private void initialize() {
jedis.hset(REDIS_KEY, "water", "0");
jedis.hset(REDIS_KEY, "last_update", String.valueOf(System.currentTimeMillis() / 1000));
}
public boolean allowRequest() {
Object result = jedis.eval(LUA_SCRIPT, 1, REDIS_KEY, String.valueOf(System.currentTimeMillis() / 1000),
String.valueOf(capacity), String.valueOf(rate));
return (Long) result == 1;
}
public void close() {
jedis.close();
}
}
滑动窗口算法(Sliding Window)
设计思路: 将时间划分为更小的子窗口(如 100ms),统计最近一个完整窗口内的请求数量。
import redis.clients.jedis.Jedis;
public class SlidingWindowRateLimiter {
private static final String REDIS_KEY_PREFIX = "sliding_window_rate_limiter:";
private final Jedis jedis;
private final int windowSize;
private final int limit;
public SlidingWindowRateLimiter(String host, int port, int windowSize, int limit) {
this.jedis = new Jedis(host, port);
this.windowSize = windowSize;
this.limit = limit;
}
public boolean allowRequest() {
long currentTime = System.currentTimeMillis();
String key = REDIS_KEY_PREFIX + (currentTime / (windowSize * 1000));
jedis.zremrangeByScore(key, 0, currentTime - windowSize * 1000);
jedis.zadd(key, currentTime, String.valueOf(currentTime));
jedis.expire(key, windowSize);
long count = jedis.zcard(key);
return count <= limit;
}
public void close() {
jedis.close();
}
}