🎯为什么需要限流
系统容量有限。突发流量(秒杀、爬虫、重试风暴)会瞬间打满 CPU/连接/线程,引发雪崩。限流 = "量力而行,超了就拒"。
核心思想:用有损服务换系统存活。拒绝 10% 的请求,好过 100% 请求都超时、整站不可用。
🎟️令牌桶(Token Bucket)
以固定速率往桶里放令牌,请求来了先拿令牌,拿到才放行,拿不到就拒绝。桶有容量上限,可应对突发。
特点:允许突发(桶里攒着的令牌可瞬间消费);长期速率受限。最常用(Guava RateLimiter、Sentinel 都支持)。
🪣漏桶(Leaky Bucket)
请求像水倒入桶,桶以恒定速率漏出(处理)。桶满则溢出(拒绝)。强调"恒定输出速率",平滑流量。
特点:不管入水多猛,出水速率恒定 → 绝对平滑,但不支持突发(多余的水直接溢出)。适合"必须匀速"的场景(如网络流量整形)。
令牌桶 vs 漏桶:令牌桶"进可突发、出受限";漏桶"进可接受、出恒定"。一个保突发、一个保平滑,按需选用。
🪟滑动窗口(Sliding Window)
把时间切成小格,统计"最近一个时间窗口内"的请求数,超过阈值就拒绝。比固定窗口更精准。
| 方式 | 说明 | 问题 |
|---|---|---|
| 固定窗口 | 每 1 秒最多 N 次 | 窗口临界处可能双倍放行(如 0.9s 和 1.1s 各 N 次) |
| 滑动窗口 | 统计最近 N 秒请求数 | 更平滑、无临界突刺,但需记录时间戳、稍重 |
实践:Redis 的
INCR + EXPIRE 可做简单固定窗口;滑动窗口常用 Redis Sorted Set(zset)存时间戳,统计窗口内数量。更省内存的变体是滑动日志/滑动计数器。⚖️三者对比
| 算法 | 突发 | 平滑 | 实现难度 | 典型 |
|---|---|---|---|---|
| 令牌桶 | ✅ 支持 | 中 | 中 | Guava/Sentinel |
| 漏桶 | ❌ 不支持 | 强 | 中 | 网络整形 |
| 滑动窗口 | ❌ | 强 | 略高 | Redis zset |
🌐分布式限流
单机限流无法约束"集群总流量"。分布式限流需要一个全局计数器,通常借助 Redis 或专门的限流组件。
方案:① 集中式(所有节点向 Redis 原子计数,如 Lua 脚本保证原子);② 网关层限流(在 API 网关统一做,最省心);③ 分桶(按用户/IP 维度分别限流,防单用户打爆)。
维度:限流可分
QPS(每秒请求数)、并发数(同时处理数)、连接数。热点用户/接口还要做"细粒度限流"(如单用户每分钟 100 次)。🎯面试要点速记
目的:用有损服务换系统存活,防雪崩。
令牌桶:按速率发令牌,桶有容量→支持突发。
漏桶:恒定速率流出→绝对平滑,不支持突发。
滑动窗口:统计最近窗口请求数,比固定窗口更准,无临界突刺。
分布式:用 Redis/Lua 或网关层做全局计数,可分维度限流。
维度:QPS / 并发 / 连接;按用户/接口/IP 细分。
必考题:"令牌桶和漏桶的区别?"——令牌桶进可突发(攒令牌瞬间消费)、漏桶出恒定(绝对平滑)。一个适合"允许短时高峰",一个适合"必须匀速"。