计数器法

我们需要选定一个时间起点,之后每当有接口请求到来,我们就将计数器加一。如果在当前时间窗口内,根据限流规则(比如每秒钟最大允许 100 次访问请求),出现累加访问次数超过限流值的情况时,我们就拒绝后续的访问请求。当进入下一个时间窗口之后,计数器就清零重新计数。


  1. public class CounterDemo {
  2. public long timeStamp = getNowTime();
  3. public int reqCount = 0;
  4. public final int limit = 100; // 时间窗口内最大请求数
  5. public final long interval = 1000; // 时间窗口ms
  6. public boolean grant() {
  7. long now = getNowTime();
  8. if (now < timeStamp + interval) {
  9. // 在时间窗口内
  10. reqCount++;
  11. // 判断当前时间窗口内是否超过最大请求控制数
  12. return reqCount <= limit;
  13. } else {
  14. timeStamp = now;
  15. // 超时后重置
  16. reqCount = 1;
  17. return true;
  18. }
  19. }
  20. }

滑动窗口

假设我们的限流规则是,每秒钟不能超过 100 次接口请求。第一个 1s 时间窗口内,100 次接口请求都集中在最后 10ms 内。在第二个 1s 的时间窗口内,100 次接口请求都集中在最开始的 10ms 内。虽然两个时间窗口内流量都符合限流要求(≤100 个请求),但在两个时间窗口临界的 20ms 内,会集中有 200 次接口请求。固定时间窗口限流算法并不能对这种情况做限制,所以,集中在这 20ms 内的 200 次请求就有可能压垮系统。
为了解决这个问题,我们可以对固定时间窗口限流算法稍加改造。我们可以限制任意时间窗口(比如 1s)内,接口请求数都不能超过某个阈值( 比如 100 次)。因此,相对于固定时间窗口限流算法,这个算法叫滑动时间窗口限流算法。


漏桶

image.png

  1. public class LeakyDemo {
  2. public long timeStamp = getNowTime();
  3. public int capacity; // 桶的容量
  4. public int rate; // 水漏出的速度
  5. public int water; // 当前水量(当前累积请求数)
  6. public boolean grant() {
  7. long now = getNowTime();
  8. water = max(0, water - (now - timeStamp) * rate); // 先执行漏水,计算剩余水量
  9. timeStamp = now;
  10. if ((water + 1) < capacity) {
  11. // 尝试加水,并且水还未满
  12. water += 1;
  13. return true;
  14. } else {
  15. // 水满,拒绝加水
  16. return false;
  17. }
  18. }
  19. }

令牌桶

image.png

  1. public class TokenBucketDemo {
  2. public long timeStamp = getNowTime();
  3. public int capacity; // 桶的容量
  4. public int rate; // 令牌放入速度
  5. public int tokens; // 当前令牌数量
  6. public boolean grant() {
  7. long now = getNowTime();
  8. // 先添加令牌
  9. tokens = min(capacity, tokens + (now - timeStamp) * rate);
  10. timeStamp = now;
  11. if (tokens < 1) {
  12. // 若不到1个令牌,则拒绝
  13. return false;
  14. } else {
  15. // 还有令牌,领取令牌
  16. tokens -= 1;
  17. return true;
  18. }
  19. }
  20. }