你好,我是风一样的树懒,一个工作十多年的后端专家,曾就职京东、阿里等多家互联网头部企业。公众号"吴计可师",已经更新了过百篇高质量的面试相关文章,喜欢的朋友欢迎关注点赞
引言 :
凌晨两点,促销活动刚开始3秒,核心服务接口因瞬间流量过载而瘫痪,导致订单大面积失败。
复盘结论:缺乏有效的接口限流 防护,让整个系统在流量洪峰前"裸奔"。
本文将带你从零开始,深入浅出,构建一套从算法原理到分布式落地的完整限流方案。
**一、为什么必须限流?
限流(Rate Limiting)不是为了让系统跑得更快,而是为了不让它死掉 。它是系统稳定性建设的基石,与缓存 、降级并称为高可用系统的"三驾马车"。
- 防止资源耗尽:避免突发流量打满CPU、内存、数据库连接等资源,导致服务雪崩。
- 应对恶意攻击**:防止CC攻击、刷单、爬虫等恶意行为,保护后端资源。
- 实现业务分级:为不同用户(如VIP、普通用户)或不同API分配不同的配额,保障核心业务。
- 削峰填谷:将突发流量整形为匀速请求,避免下游服务被压垮。
flowchart TD
A[海量用户请求] --> B[负载均衡层]
B --> C[限流
第一道防线
拒绝过量请求] C --> D[服务层] D --> E[降级
第二道防线
弃卒保帅] E --> F[缓存/数据库] F --> G[熔断
第三道防线
快速失败
防止雪崩]
第一道防线
拒绝过量请求] C --> D[服务层] D --> E[降级
第二道防线
弃卒保帅] E --> F[缓存/数据库] F --> G[熔断
第三道防线
快速失败
防止雪崩]
二、核心限流算法:原理、场景与抉择
1. 固定窗口计数器(Fixed Window)
- 原理:将时间划分为固定窗口(如1分钟),统计窗口内请求数,超限则拒绝。
- 实现 :
Redis
的INCR
和EXPIRE
命令。 - 优点:实现简单,内存占用少。
- 缺点 :临界突变问题 。在两个窗口临界点,可能瞬间通过
2 * limit
个请求。
适用场景:对精度要求不高的简单场景,如限制短信验证码发送频率。
2. 滑动窗口(Sliding Window)
- 原理:将固定窗口细分为更小的时间片(如1分钟的窗口分为60个1秒的片),滑动地统计最近一个窗口内的请求数。
- 实现 :使用
Redis
的ZSET
(有序集合)或List
结构,存储时间戳并清理过期数据。 - 优点:有效解决临界问题,精度较高。
- 缺点:比固定窗口更耗内存。
适用场景:大多数业务场景,是精度和复杂度之间的良好平衡。
3. 漏桶算法(Leaky Bucket)
- 原理 :请求先进入一个容量固定的"桶"中,桶以恒定的速率漏水(处理请求)。桶满则丢弃新请求。
- 优点 :平滑突发流量,输出流量始终是恒定的,非常适合保护下游系统。
- 缺点:无法应对突发流量,即使系统有空闲资源,请求也必须排队等待。
适用场景:保护数据库、第三方API等下游系统,确保请求速率绝对均匀。
4. 令牌桶算法(Token Bucket)
- 原理 :系统以恒定的速率向一个容量固定的"桶"中添加令牌。请求到达时,必须获取到一个令牌才能被处理,否则被拒绝。
- 优点 :既允许突发流量(桶中有令牌时可一次性取用),又能限制长期平均速率。
- 缺点:实现相对复杂。
适用场景 :绝大多数API限流场景 。它是弹性应对突发流量和保护系统的最佳平衡方案,也是 Google Guava
和 Amazon
等主流方案的选择。
quadrantChart
title 四大限流算法选型象限
x-axis "处理简单" --> "处理复杂"
y-axis "限制突发" --> "允许突发"
"固定窗口": [0.1, 0.1]
"滑动窗口": [0.4, 0.3]
"漏桶": [0.6, 0.8]
"令牌桶": [0.9, 0.9]
三、分布式限流实战:基于Redis的令牌桶实现
单机限流可用 Guava
,但分布式系统必须依赖中间件,如 Redis
。
lua
-- Redis Lua 脚本实现令牌桶(原子操作)
local key = KEYS[1] -- 限流的键(如: user:123:api_order)
local limit = tonumber(ARGV[1]) -- 桶容量
local rate = tonumber(ARGV[2]) -- 令牌生成速率(个/秒)
local now = tonumber(ARGV[3]) -- 当前时间戳
local requested = tonumber(ARGV[4]) -- 本次请求的令牌数(默认为1)
local last_time_key = key .. ':time'
local tokens_key = key .. ':tokens'
-- 1. 获取当前桶中的令牌数和最后更新时间
local last_tokens = tonumber(redis.call('get', tokens_key)) or limit
local last_time = tonumber(redis.call('get', last_time_key)) or now
-- 2. 计算时间差,并生成新令牌
local time_passed = now - last_time
local new_tokens = time_passed * rate -- 期间应生成的令牌数
local current_tokens = math.min(limit, last_tokens + new_tokens)
-- 3. 判断是否有足够令牌
if current_tokens < requested then
-- 令牌不足,拒绝
return 0
else
-- 4. 扣减令牌,更新时间和令牌数
current_tokens = current_tokens - requested
redis.call('set', tokens_key, current_tokens)
redis.call('set', last_time_key, now)
-- 设置过期时间,避免冷数据长期占用内存(设置为窗口时间的2倍)
redis.call('expire', tokens_key, math.ceil(limit/rate) * 2)
redis.call('expire', last_time_key, math.ceil(limit/rate) * 2)
return 1 -- 允许通过
end
调用示例(Spring Boot):
java
@Component
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean tryAcquire(String key, int limit, int rate) {
long now = System.currentTimeMillis() / 1000;
String luaScript = "..."; // 上面的Lua脚本
RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(key),
String.valueOf(limit),
String.valueOf(rate),
String.valueOf(now),
"1");
return result == 1;
}
}
四、生产环境最佳实践与架构设计
1. 限流维度要灵活
- 全局限流:限制整个集群对某个接口的总QPS。
- 用户限流 :
user_id:api_path
,防止单用户滥用。 - IP限流 :
ip:api_path
,防止单IP攻击。 - 参数限流:如对同一个商品ID的查询进行限流。
2. 限流规则动态化
不要将规则硬编码在代码中!应配置在 Nacos
、Apollo
、ZooKeeper
等配置中心,支持实时推送和修改。
3. 优雅响应与降级
被限流的请求不应粗暴返回 429 Too Many Requests
,应提供友好 fallback:
- 返回队列位置:告知用户预计等待时间。
- 返回默认值:如商品详情页限流,返回缓存中的静态信息。
- 请求排队:将请求转入消息队列,异步处理。
4. 分层限流与集群限流
- 分层:在网关(Nginx/Spring Cloud Gateway)、应用层、DB层分别设置限流。
- 集群同步 :对于严格全局限流,可使用
Redis
或专门的限流中间件(如Sentinel
)来同步集群状态。
总结与展望
关键点 | 核心方案 | 工具选型 |
---|---|---|
算法核心 | 令牌桶(允许突发)、漏桶(平滑流量) | Guava, Redis + Lua |
分布式实现 | Redis(原子操作)、中间件 | Redis, Sentinel, Nginx |
规则管理 | 动态配置 | Apollo, Nacos |
架构部署 | 网关层 + 应用层分层限流 | Spring Cloud Gateway, Zuul |
限流设计没有银弹,只有最适合业务场景的方案。从理解算法原理开始,到选择合适的工具落地,再到构建全方位的监控体系,每一步都是构建高可用系统不可或缺的环节。
今天文章就分享到这儿,喜欢的朋友可以关注我的公众号,回复"进群",可进免费技术交流群。博主不定时回复大家的问题。 公众号:吴计可师