常见的限流算法

限流算法是用于控制访问频率、保护系统免受过载攻击的重要手段。常见的限流算法有以下几种,每种算法都有不同的应用场景和优缺点。下面是几种常见的限流算法的详细介绍:

1. 计数器算法(Counter)

原理

计数器算法是最简单的限流算法。它在固定的时间窗口内记录请求的次数,一旦请求次数达到预设的上限,后续请求会被拒绝。

实现步骤
  • 设置一个时间窗口(例如 1 秒、1 分钟等)。
  • 使用一个计数器记录在当前时间窗口内的请求数量。
  • 如果计数器达到限制次数,则拒绝后续请求;否则增加计数器。
  • 当时间窗口结束时,重置计数器。
优点
  • 实现简单,适合处理短时间内的高并发请求。
缺点
  • 临界问题:在时间窗口的边界处可能出现"流量突发",例如在窗口末尾和下一个窗口的开始时连续发出大量请求。
使用场景

适用于简单的限流场景,比如 API 接口每秒最多允许多少次请求。

代码示例(伪代码)
复制代码

java

复制代码

class CounterLimiter { private int maxRequests; private int currentCount; private long windowStart; private long windowDuration; public boolean allowRequest() { long now = System.currentTimeMillis(); if (now - windowStart > windowDuration) { windowStart = now; currentCount = 0; } if (currentCount < maxRequests) { currentCount++; return true; } else { return false; } } }

2. 滑动窗口算法(Sliding Window)

原理

滑动窗口算法是对计数器算法的一种优化,它将时间窗口进一步划分为多个小的时间片段,从而避免计数器算法的临界问题。每个小窗口内维护一个计数器,当请求到达时,滑动窗口根据当前时间计算跨越的时间片段,并更新计数器。

实现步骤
  • 将大时间窗口(如 1 分钟)分成多个小时间窗口(如 10 秒),并为每个小时间窗口维护请求计数。
  • 每次请求到达时,判断它属于哪个小窗口,并更新相应计数器。
  • 同时计算过去的所有小窗口总请求数,如果总数超过限流阈值,则拒绝请求。
优点
  • 缓解了计数器算法的临界问题,流量分布更加平滑。
缺点
  • 实现相对复杂,需要额外的存储空间维护多个小时间窗口。
使用场景

适合希望更加平滑限流的场景,尤其是对流量有一定均匀要求的服务。

代码示例(伪代码)
复制代码

java

复制代码

class SlidingWindowLimiter { private int[] windows; private long windowSize; private int maxRequests; public boolean allowRequest() { long now = System.currentTimeMillis(); int currentWindowIndex = getWindowIndex(now); updateWindow(now); int totalRequests = getTotalRequests(); if (totalRequests < maxRequests) { windows[currentWindowIndex]++; return true; } else { return false; } } // 辅助方法省略 }

3. 漏桶算法(Leaky Bucket)

原理

漏桶算法可以看作是一个水桶模型,桶的容量代表系统能处理的最大请求数,请求是向桶中注水,水以固定速率流出。如果桶满了,则会丢弃新的请求。

实现步骤
  • 创建一个固定容量的桶。
  • 请求到达时将其放入桶中,桶以固定的速率处理请求。
  • 如果桶已满,拒绝新请求。
优点
  • 请求处理速率是固定的,能够很好地平滑突发流量。
缺点
  • 固定处理速率可能无法适应某些突发流量大的场景,可能导致部分请求被丢弃。
使用场景

适用于处理具有固定速率的请求处理场景,如网络流量控制等。

代码示例(伪代码)
复制代码

java

复制代码

class LeakyBucketLimiter { private int bucketSize; private long lastLeakTime; private int currentWater; private int leakRate; public boolean allowRequest() { long now = System.currentTimeMillis(); leak(now); if (currentWater < bucketSize) { currentWater++; return true; } else { return false; } } private void leak(long now) { int leakedWater = (int) ((now - lastLeakTime) * leakRate); currentWater = Math.max(0, currentWater - leakedWater); lastLeakTime = now; } }

4. 令牌桶算法(Token Bucket)

原理

令牌桶算法类似于漏桶算法,但它允许一定程度的突发流量。系统以固定的速率生成令牌,令牌存放在桶中。每次请求需要从桶中获取令牌,如果桶中有足够的令牌,则允许请求通过,否则拒绝。

实现步骤
  • 设置一个容量为 bucketSize 的桶,用于存储令牌。
  • 系统以固定速率生成令牌,如果桶未满,令牌将被加入桶中。
  • 请求到达时,尝试从桶中获取令牌,如果有足够的令牌则允许请求,否则拒绝。
优点
  • 支持突发流量,同时保证了整体请求处理的速率。
缺点
  • 实现略微复杂,需要维护生成令牌的过程。
使用场景

适合处理偶发性高并发的场景,如 API 限流和保护下游服务。

代码示例(伪代码)
复制代码

java

复制代码

class TokenBucketLimiter { private int bucketSize; private int tokens; private int refillRate; private long lastRefillTime; public boolean allowRequest() { refill(); if (tokens > 0) { tokens--; return true; } else { return false; } } private void refill() { long now = System.currentTimeMillis(); int newTokens = (int) ((now - lastRefillTime) * refillRate); tokens = Math.min(bucketSize, tokens + newTokens); lastRefillTime = now; } }

5. Redis 计数限流

原理

Redis 可以通过其 INCREXPIRE 命令实现简单高效的限流。每次请求到达时,使用 INCR 增加 Redis 中的计数器,如果计数器超过阈值,则拒绝请求。同时,使用 EXPIRE 为计数器设置过期时间。

实现步骤
  • 每个请求到来时对 Redis 中的计数器进行 INCR
  • 如果计数器在一个时间窗口内超过设定的阈值,则限流。
  • EXPIRE 命令设置计数器的过期时间,以实现限流的时间窗口控制。
优点
  • 分布式环境中非常适合,支持高并发请求的计数和过期时间管理。
缺点
  • Redis 需要额外的存储和网络开销。
使用场景

适用于分布式系统中的限流,尤其是在 API 网关和微服务架构中。

代码示例(伪代码)
复制代码

java

复制代码

class RedisRateLimiter { private RedisClient redis; private String key; private int maxRequests; private int windowTime; public boolean allowRequest() { int count = redis.incr(key); if (count == 1) { redis.expire(key, windowTime); } return count <= maxRequests; } }

总结

算法 优点 缺点 使用场景
计数器算法 实现简单,适合小型项目 临界点可能导致突发流量 简单限流
滑动窗口算法 平滑计数器算法的缺点,避免流量突发 需要更多的存储空间和实现复杂度 流量分布较平滑的场景
漏桶算法 固定处理速率,流量控制稳定 突发流量支持不佳,可能丢弃请求 固定速率的服务
令牌桶算法 支持突发流量,保证请求处理的平滑 实现复杂,维护令牌生成 支持突发流量的限流场景
Redis 限流 分布式限流简单高效 需要依赖 Redis,增加网络开销
相关推荐
晴子呀8 分钟前
Spring底层原理大致脉络
java·后端·spring
DreamByte9 分钟前
Python Tkinter小程序
开发语言·python·小程序
只吹45°风14 分钟前
Java-ArrayList和LinkedList区别
java·arraylist·linkedlist·区别
覆水难收呀18 分钟前
三、(JS)JS中常见的表单事件
开发语言·前端·javascript
阿华的代码王国22 分钟前
【JavaEE】多线程编程引入——认识Thread类
java·开发语言·数据结构·mysql·java-ee
黑蛋同志22 分钟前
array和linked list的区别
java
繁依Fanyi28 分钟前
828 华为云征文|华为 Flexus 云服务器部署 RustDesk Server,打造自己的远程桌面服务器
运维·服务器·开发语言·人工智能·pytorch·华为·华为云
andrew_121928 分钟前
腾讯 IEG 游戏前沿技术 一面复盘
java·redis·sql·面试
寻求出路的程序媛36 分钟前
JVM —— 类加载器的分类,双亲委派机制
java·jvm·面试
这孩子叫逆38 分钟前
35. MyBatis中的缓存失效机制是如何工作的?
java·spring·mybatis