在高并发架构中,限流是保障服务高可用的“安全阀”。当上游流量洪峰超出下游服务的处理极限时,限流机制通过精准的拒绝、排队或延迟策略,防止系统因资源耗尽而发生雪崩。
本文将从限流的分类入手,深入剖析单机限流的核心算法逻辑,并详解分布式全局限流的两种主流架构,为你构建一套完整的限流知识体系。
为什么需要限流?
在分布式系统中,限流(Rate Limiting)是下游服务为了防止被上游请求压垮,而采取的一种自我保护机制。
目标:控制请求速率,保证系统稳定性
分类:
频控:控制用户在N秒内只可执行M次操作。
单机限流:某服务的每台服务器在1s内最多可处理"个请求。
全局限流:某服务在1s内总共可处理M个请求。
单机自适应限流:某服务的每台服务器根据自身服务状况,使用各种自动化算法动态判断是否限制请求。
频控
用户维度下的限流
场景:频控主要用于防止恶意刷接口或限制用户行为。
N秒内只能执行1次
实现原理:使用SETNX命令。如果设置成功,说明是首次操作;如果失败,说明在时间窗口内已操作过。
// 对应 Redis 命令: SET key 0 EX 30 NX Boolean result = redisTemplate.opsForValue().setIfAbsent(key, 0, 30, TimeUnit.SECONDS);N秒内最多执行 M 次
当涉及到“计数”和“过期时间设置”两个动作时,必须保证原子性,否则可能出现 Key 永久存在或计数错乱。
实现逻辑:使用INCR计数,当返回值为 1 时设置过期时间。
判断逻辑:
count ≤ M → 放行
count > M → 限流
local count = redis.call('INCR', KEYS[1]) if count == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) -- ARGV[1] 为过期秒数 end return count单机限流
控制“单台机器”的处理能力
固定时间窗口
原理:将时间轴切分为固定长度的窗口(如1s),每个窗口维护一个计数器。
执行流程:
定位窗口:根据当前时间戳,计算出所属的窗口ID(例如
当前秒数)。获取计数器:从内存中获取该窗口ID对应的计数器。
判断与扣减:
如果计数器值 > 0:计数器减1,允许请求通过。
如果计数器值 = 0:拒绝请求。
缺点:无法防止时间窗口边界附近的流量暴增。
举例:假设限制1s内100个请求。在0.9s时涌入100个请求,在1.1s时又涌入100个请求。虽然每个窗口都没超限,但在1.0s前后的200ms内,系统实际承受了200个请求,导致瞬间流量翻倍。
滑动时间窗口
原理:将时间窗口划分为更小的格子。窗口随着时间滑动,丢弃过期的槽位,累加新槽位的计数。
流程:
划分时间槽:将时间线按小粒度(如50ms)划分为独立的时间槽。
定义滑动窗口:限流窗口由最近的N个槽(如20个)组成,随时间推移不断向后滑动。
请求判定:
当请求到来时,在当前窗口内查找是否存在计数大于0的槽。
若找到:将该槽计数减1,允许请求通过。
若未找到(所有槽计数均为0):丢弃请求,触发限流。
优点:解决了窗口边界流量暴增问题,精度更高。
缺点:但需要维护更多的槽位数据,内存和CPU开销相对较大。
漏桶算法
原理:请求像水一样流入桶中,桶以恒定的速率出水(处理请求)。无论流入速率多快,流出速率永远固定。
执行流程:
请求到达:检查漏桶(队列)是否已满。
判断与入队:
如果桶未满:将请求加入队列尾部,等待处理。
如果桶已满:直接拒绝请求。
匀速处理:一个独立的消费者线程以固定的时间间隔(如每10ms)从队列头部取出一个请求进行处理。
特点:强行整形,将突发流量平滑为恒定流量,按照固定的速率被处理。
缺点:无法应对突发流量。
令牌桶算法
原理:系统以恒定速率向桶中放入令牌,桶有容量上限。存满新生成的令牌会被丢弃,请求处理前必须先获取令牌,无令牌则拒绝。
适用场景:对流量精度高,并且容忍短期突发流量。
优点:既控制长期的平均速率,又支持短期突发流量。
缺点:实现复杂度较高。(要维护令牌的生成,存储,消耗)
全局限流
基于Redis的集中式限流
不适合请求量巨大的服务
逻辑:把单机的固定窗口计数器搬到 Redis 上。
流程:所有服务器实例在处理请求前,先去 Redis 做一次INCR。如果 Redis 返回的值超过总限额,本地拦截。
优点:数据强一致,限流精准。
缺点:引入额外网络调用,Redis本身可能成为性能瓶颈,影响了业务服务的稳定性。
基于时间片的动态反馈限流
适合请求量大的服务,对请求进行粗略筛选。
核心逻辑:上报统计 -> 计算比例 -> 下发丢弃率。
流程:
统计上报:各服务实例周期性(如每1s)将本地的请求数、延迟等统计信息上报到消息队列。
全局聚合:日志收集器消费消息,计算整个集群在当前时间片的实际QPS。
决策下发:限流服务器对比实际QPS与目标阈值,计算丢弃率(DropRate)。
公式:
DropRate = (实际QPS - 目标QPS) / 实际QPS
本地执行:各实例接收最新的DropRate,在本地进行概率性丢弃。
优点:避免了每次请求都访问Redis,性能极高。
缺点:存在秒级的控制延迟,无法做到毫秒级的精准控制,适合对实时性要求不苛刻的场景。