内容提要
限流控制请求进入系统的速率,防止突发流量耗尽资源。固定窗口计数简单,但窗口边界可能放行双倍突发;滑动窗口按最近时间段计数,更精确但开销大。漏桶以固定速率排队释放请求,平滑流量;令牌桶按速率补充令牌,允许短时突发。分布式场景需用 Redis 等共享配额并原子执行,同时考虑 Redis 故障时的降级策略。
延伸解读
固定窗口的边界突发风险
固定窗口计数简单,但窗口边界可能放行双倍突发。例如限流60次/分钟,在59.9秒和60.1秒各到达60个请求,两个窗口各自未超限,但0.2秒内实际放行120个请求。这是因为固定窗口只限制每个对齐窗口内的总量,而非任意连续时间区间。若业务对瞬时突发敏感,需考虑滑动窗口或令牌桶等更平滑的方案。
漏桶与令牌桶的核心差异
漏桶以固定速率排队释放请求,平滑流量,但需要实际队列和调度器;仅计算水位只能决定是否接受,不能自动延迟执行。令牌桶按速率补充令牌,允许短时突发,桶中存放的是许可而非排队请求。选择时需明确:若后端需要稳定输入速率,漏桶更合适;若允许短时突发且希望减少排队延迟,令牌桶更灵活。
分布式限流的共享配额与原子性
多网关实例各自维护本地配额会导致总配额放大,例如三个网关各10次/秒,用户实际可获30次/秒。使用Redis共享令牌桶并配合Lua脚本原子执行读取、补充、检查和扣减,可避免并发下超额放行。同时需提前规划Redis故障时的降级策略:拒绝请求、临时本地配额或放行并告警,并监控429响应数和Redis延迟,防止限流检查本身成为瓶颈。
Q&A
限流和并发控制有什么区别?
限流控制的是单位时间内允许进入系统的请求数量(请求速率),而并发控制限制的是同时正在执行、尚未完成的请求数量。例如,限流可以设置为每秒最多100个请求,而并发控制可以设置为同时最多执行20个请求。即使请求速率相同,如果每个请求的处理时间变长,并发占用的资源也会显著增加,因此对于慢接口可能需要同时限制并发数。
固定窗口限流在窗口边界处为什么会导致突发流量通过?
固定窗口将时间划分为固定长度的窗口,每个窗口独立计数。在窗口切换时计数器会重置。例如,限制每分钟60次,在59.9秒时到达的60个请求属于第一个窗口并被允许,在60.1秒时到达的60个请求属于第二个窗口,计数器已重置,也被允许。两个窗口各自都没有超限,但实际在0.2秒内通过了120个请求,形成了边界突发。
滑动窗口限流如何避免固定窗口的边界问题?
滑动窗口不依赖固定的时间边界,而是以当前请求时间为终点,统计过去一个窗口长度内的请求数。例如,在60.1秒时检查过去60秒(即0.1秒到60.1秒)内的请求,59.9秒通过的60个请求仍然在统计范围内,因此新到达的60个请求会被拒绝。实现方式有两种:记录每个请求的时间戳(滑动日志),或使用多个短时间桶的计数(分桶计数),后者以少量精度损失换取更低开销。
漏桶算法和令牌桶算法在应对突发流量时有什么不同?
漏桶算法将请求放入固定容量的队列,并以固定速率释放请求,从而平滑流量,但被接受的突发请求可能需要排队等待。令牌桶算法则以固定速率向桶中添加令牌,请求需要获取令牌才能执行,桶的容量决定了可以积累的突发额度,因此允许短时突发(使用积累的令牌),但长期平均速率仍受限制。简单说,漏桶控制输出速率,令牌桶允许一定程度的突发。
在分布式系统中如何实现全局限流?
在分布式系统中,多个网关实例需要共享同一个限流配额,否则每个实例独立限流会导致总配额翻倍。通常使用 Redis 等集中存储来维护共享的令牌桶或计数器,并通过 Redis Lua 脚本将读取、补充、检查和扣减等操作原子化执行,避免并发问题。同时需要考虑 Redis 故障时的降级策略,例如拒绝请求、临时使用本地配额或放行并告警。
令牌桶算法中令牌是如何补充的?需要定时任务吗?
令牌桶不需要定时任务来添加令牌。令牌的补充是根据距离上次请求的时间差计算的:每次请求到达时,根据流逝的时间乘以补充速率,增加令牌数量,但不超过桶的容量。公式为:tokens = min(B, previous_tokens + r × elapsed_time)。这样既简单又高效,但需要注意时间单位一致,并在单机上使用单调时钟以避免系统时间调整的影响。