redis之简单限流

什么是接口限流

  为什么要有接口限流?场景如下:1.接口限流,比如我们对外一个接口,我们要限制一个用户单位时间访问次数?2.系统要限定用户的某个行为在指定的时间A里只能允许发生 N 次。

实现方式

  我们用zset来实现这个方案。我们将用户id和事件id生成这个zset的容器id,当发生一次时,我们将当前时间作为score,同时也作为value,放到集合里面。然后删除这个元素加入前限定时间A之外的数据。然后将集合的所有数据设置为过了A时间就过期.然后取出这个集合有多少数据,判断是否超标。

public boolean isActionAllowed(String userId, String     actionKey, int period, int maxCount) {
    String key = String.format("hist:%s:%s", userId, actionKey);
    long nowTs = System.currentTimeMillis();
    Pipeline pipe = jedis.pipelined();
    pipe.multi();
    pipe.zadd(key, nowTs, "" + nowTs);
    pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
    Response<Long> count = pipe.zcard(key);
    pipe.expire(key, period + 1);
    pipe.exec();
    pipe.close();
    return count.get() <= maxCount;

}

漏斗限流策略

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);
}
return funnel.watering(1); // 需要1个quota
}
}

  
  Funnel 对象的 make_space 方法是漏斗算法的核心,其在每次灌水前都会被调用以触发漏水,给漏斗腾出空间来。能腾出多少空间取决于过去了多久以及流水的速率。Funnel 对象占据的空间大小不再和行为的频率成正比,它的空间占用是一个常量。
  我们观察 Funnel 对象的几个字段,我们发现可以将 Funnel 对象的内容按字段存储到一个 hash 结构中,灌水的时候将 hash 结构的字段取出来进行逻辑运算后,再将新值回填到 hash 结构中就完成了一次行为频度的检测。

新方案

  redis4.0出了一个新的模块。叫redis-cell。该模块也使用了漏斗算法,且提供了限流指令。
  cl.throttle sd:sd 15 30 60
  意思是15的容量,每60秒就丢弃30个元素。