本文大部分内容引自《Redis深度历险:核心原理和应用实践》,感谢作者!!!
漏斗限流灵感
限流算法
漏斗限流:https://blog.csdn.net/dadiyang/article/details/82887663
令牌桶限流:https://www.cnblogs.com/cjsblog/p/9379516.html
漏斗限流单机实现
public class FunnelRateLimiter {
static class Funnel {
//容量
int capacity;
//流出速率
float leakingRate;
//剩余容量
int leftQuota;
//计算起始时间
long leakingTs;
public Funnel(int capacity, float leakingRate) {
this.capacity = capacity;
this.leakingRate = leakingRate;
this.leftQuota = capacity;
this.leakingTs = System.currentTimeMillis();
}
void makeSpace() {
long nowTs = System.currentTimeMillis();
long deltaTs = nowTs - leakingTs;
int deltaQuota = (int) (deltaTs * leakingRate);
if (deltaQuota < 0) {
//间隔时间太长,整数数字过大溢出
this.leftQuota = capacity;
this.leakingTs = nowTs;
return;
}
if (deltaQuota < 1) {
//腾出空间太小,最小单位是1
return;
}
//剩余容量 = 当前容量 + 流出速率 * 间隔时间
this.leftQuota += deltaQuota;
this.leakingTs = nowTs;
if (this.leftQuota > this.capacity) {
this.leftQuota = this.capacity;
}
}
//判断是否能加入交易
boolean watering(int quota) {
makeSpace();
if (this.leftQuota >= quota) {
this.leftQuota -= quota;
return true;
}
return false;
}
}
private Map<String, Funnel> funnels = new HashMap<>();
public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {
String key = String.format("%s:%s", userId, actionKey);
Funnel funnel = funnels.get(key);
if (funnel == null) {
funnel = new Funnel(capacity, leakingRate);
funnels.put(key, funnel);
}
//需要1个quota
return funnel.watering(1);
}
}
Redis漏斗限流使用方法
允许用户回复行为的频率为每60s最多30次(漏水速率),漏斗的初始容量为15,也就是说一开始可以连续回复15个帖子,然后才开始受影响,每次行为占用1个空间。恢复速率是30/60s=0.5个/s
cl.throttle [key] [capacity] [operation_times] [time_span] [quota]
cl.throttle laoqian:reply 15 30 60 1
1) (integer) 0 # 0表示允许,1表示拒绝
2) (integer) 15 # 漏斗容量capacity
3) (integer) 14 # 漏斗剩余空间left_quota
4) (integer) -1 # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)
在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的重试时间都计算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想阻塞线程,也可以异步定时任务来重试