- 漏桶:
package cn.gw.common.traffic;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
/**
* @author gaowu
* @date 2022/9/20 15:11
*/
@Component
public class RedisLeakyBucketTrafficLimiter implements ILeakyBucketTrafficLimiter {
/**
* $lb = LeakyBucket
**/
private static final String GROUP_KEY = "$lb";
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* KEYS[1] = GROUP_KEY
* KEYS[2] = water 上次水量
* KEYS[3] = time 上次请求时间(s)
* ARGV[1] = capacity 最大蓄水量
* ARGV[2] = rate 水流速率/s
* ARGV[3] = now_time 当前请求时间(s)
* ARGV[4] = acquire 请求个数
*/
private static final String LEAKEY_BUCKET_SCRIPT = " local capacity = tonumber(ARGV[1])" +
" local rate = tonumber(ARGV[2])" +
" local now = tonumber(ARGV[3])" +
" local acquire = tonumber(ARGV[4])" +
// 上次请求后桶中水量
" local water = tonumber(redis.call('hget', KEYS[1] , KEYS[2]) or 0) " +
// 上次请求时间(/s)
" local time = tonumber(redis.call('hget', KEYS[1] , KEYS[3]) or now) " +
// 当前桶中的水量 = 上次请求后桶中水量 - 上次到现在流出的水量
" water = math.max(0, water - (now - time) * rate)" +
// 设置请求时间(/s)
" redis.call('hset' , KEYS[1] ,KEYS[3] , now)" +
// 判断桶中水是否满了
" if (water + acquire <= capacity) then" +
// 未满注水
" redis.call('hset' , KEYS[1] , KEYS[2] , water + acquire)" +
" return 1" +
" else" +
// 满了直接返回
" return 0" +
" end";
@Override
public Long limit(Long capacity, Long rate, String key) {
return limit(capacity, rate, 1L, key);
}
public Long limit(Long capacity, Long rate, Long acquire, String key) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LEAKEY_BUCKET_SCRIPT, Long.class);
List<String> keys = Arrays.asList(GROUP_KEY, key + ":water", key + ":time");
return redisTemplate.execute(redisScript, keys, capacity, rate, System.currentTimeMillis() / 1000, acquire);
}
}
2.令牌桶:
package cn.gw.common.traffic;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
/**
* @author gaowu
* @date 2022/9/21 15:48
*/
@Component
public class RedisTokenBucketTrafficLimiter implements ITokenBucketTrafficLimiter {
/**
* $tb = TokenBucket
**/
private static final String GROUP_KEY = "$tb";
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* KEYS[1] = GROUP_KEY
* KEYS[2] = residual 上次剩余token数量
* KEYS[3] = time 上次请求时间
* ARGV[1] = capacity token容量
* ARGV[2] = rate token生成速率/s
* ARGV[3] = now_time 当时间请求(s)
* ARGV[4] = default_token 默认token数量
* AVG[5] = acquire_token 获取token数据量
*/
private static final String TOKEN_BUCKET_SCRIPT = " local capacity = tonumber(ARGV[1])" +
" local rate = tonumber(ARGV[2])" +
" local now = tonumber(ARGV[3])" +
" local default_token = tonumber(ARGV[4])" +
" local acquire_token = tonumber(ARGV[5])" +
// 上次请求后桶中剩余的令牌数量
" local residual_token = tonumber(redis.call('hget', KEYS[1] , KEYS[2]) or default_token)" +
// 上次请求时间
" local time = tonumber(redis.call('hget', KEYS[1] , KEYS[3]) or now)" +
// 设置请求时间为now
" redis.call('hset' , KEYS[1] , KEYS[3] , now)" +
// 当前令牌数 = 上次请求后桶中剩余的令牌数量 + 上次到现在生成的令牌数量
" local current_token = math.min(residual_token + (now - time) * rate , capacity)" +
// 判断令牌数量是否够本次申请
" if (current_token -acquire_token >= 0) then" +
// 够减去桶中令牌数量
" redis.call('hset' , KEYS[1] , KEYS[2] , current_token - acquire_token)" +
" return 1" +
" else" +
// 不够直接返回
" return 0" +
" end";
public Long limit(Long capacity, Long rate, Long defaultToken, Long acquire, String key) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(TOKEN_BUCKET_SCRIPT, Long.class);
List<String> keys = Arrays.asList(GROUP_KEY, key + ":token", key + ":time");
return redisTemplate.execute(redisScript, keys, capacity, rate, System.currentTimeMillis() / 1000, defaultToken, acquire);
}
public Long limit(Long capacity, Long rate, Long defaultToken, String key) {
return limit(capacity, rate, defaultToken, 1L, key);
}
}
3.滑块(单机):
/**
* @author gaowu
* @date 2023/2/18 16:07
*/
public class FlowControl {
private int capacity = 1000;
private int index = 0;
private long[] controls;
private int timeIntervals = 1000;
public boolean limit(long time) {
if (controls[index] == 0 || time - controls[index] < timeIntervals) {
controls[index] = time;
index = (index + 1) % capacity;
return true;
}
return false;
}
public FlowControl(int capacity, int timeIntervals) {
if (capacity <= 0) {
capacity = this.capacity;
}
if (timeIntervals <= 0) {
timeIntervals = this.timeIntervals;
}
this.capacity = capacity;
this.controls = new long[capacity];
this.timeIntervals = timeIntervals;
}
public FlowControl() {
}