削峰填谷
某个大型活动期间,流量暴增。
活动概况
活动
双 11 促销客户类型
大量电商客户持续时间
24 小时平时流量
10 万次/天活动流量
100 万次/天10 倍增长峰值流量
平时 QPS 的 20 倍流量分析
平时
日均10 万次/天
QPS1.2 次/秒
活动期间
日均100 万次/天
QPS11.5 次/秒
峰值200 次/秒(持续 10 分钟)
数据库
50 次/秒应用服务器
100 次/秒结论:峰值流量(200 次/秒)远超系统容量(数据库 50 + 应用 100 = 150 次/秒)
削峰填谷的核心不是「加个队列」,而是把「接收请求」和「处理任务」拆开:入口只负责快速接单,真正的处理交给消费者慢慢做。这样用户的等待时间不再由瞬时峰值决定,而是由队列长度决定。
架构演进:同步 vs 异步
用户请求应用服务器数据库(阻塞等待)
用户请求应用服务器消息队列
立即返回(接受)
后端消费 & 写入数据库
这一节把削峰方案落到可执行的配置上:队列怎么划分、API 怎么改成异步、用户怎么拿到结果,以及跑起来之后怎么盯住队列的堆积情况。
1. 消息队列选择
使用 RabbitMQ:
为什么选择 RabbitMQ:
- 可靠性高(持久化、确认机制)
- 支持多种消息模式
- 社区活跃
- 有管理界面
选型边界
为什么用 RabbitMQ 做削峰
- 触发问题
- 活动流量在短时间内冲高,后端处理能力稳定但入口瞬时请求远超容量,需要把“接收请求”和“处理任务”解耦。
- 候选方案
- 应用内队列、Redis Stream、RabbitMQ、Kafka、直接扩容应用实例。
- 选择理由
- RabbitMQ 的确认、持久化和死信队列更适合当前这种任务可靠投递场景,运维复杂度也低于 Kafka。
- 代价
- 用户无法立即拿到最终结果,需要设计任务状态查询、重试、超时和失败提示。
- 暂不解决
- 暂不做大规模日志流式处理;如果未来吞吐变成百万级持续写入,再评估 Kafka。
2. 队列设计
队列设计要解决四件事:什么进队列、队列最多堆多少、消费者能吃多快、失败了怎么办。这几个参数直接决定了活动期间系统是稳还是崩。
设计流程
2. 队列设计
- 步骤 1:按任务类型划分队列,把可以延后处理的请求和必须同步返回的请求分开
- 步骤 2:设置队列容量上限和消费者并发数,让消费速度与外部 API 的承载能力匹配
- 步骤 3:开启消息持久化和手动确认,处理成功后才 ack,保证任务不丢
- 步骤 4:配置重试次数与死信队列,超过重试上限的消息进入死信队列等待人工处理
关注点:队列划分依据、容量上限与消息可靠性配置。
3. API 处理改为异步
接口不再等结果,而是先生成一个任务 ID 就返回。代价是用户拿不到即时结果,所以状态查询和失败提示必须同时设计好,否则异步很容易被当成「接口坏了」。
设计流程
3. API 处理改为异步
- 步骤 1:把原本同步完成的业务拆成接收请求与执行任务两段,接口只做校验和投递
- 步骤 2:投递成功后立即返回任务 ID 与处理中状态,让前端能据此展示进度
- 步骤 3:消费者按自身能力匀速消费队列,把瞬时高峰摊平成稳定速率
- 步骤 4:为每个任务记录状态流转(排队中、处理中、成功、失败),失败时把原因回写给用户
关注点:接口与任务的拆分边界、状态回传与失败原因可见性。
4. 查询结果
异步之后用户拿到的是一个任务 ID,这个查询接口就成了用户与系统之间的状态窗口:它要说清楚任务到哪一步了、大概什么时候能拿到结果。
设计流程
4. 查询结果
- 步骤 1:用返回的任务 ID 查询任务状态记录,已经完成的任务直接返回结果
- 步骤 2:任务仍在队列中时返回处理中状态和预计等待时间,而不是报错
- 步骤 3:任务失败时返回失败原因与重试入口,让用户能判断是否重新提交
- 步骤 4:对已完成的结果做短期缓存,避免用户反复轮询把读流量再打回服务
关注点:任务状态查询语义、等待时间提示与轮询流量控制。
队列把压力接住了,但堆积本身也是风险。队列管理要盯的就是两件事:堆积速度有多快,以及消费速度能不能追得上来,两者的差值才是真正的风险信号。
监控队列长度
队列监控的意义在于提前发现「消费不动了」。只看队列长度不够,还得结合消费速率和单条耗时,才能判断是流量涨了还是消费者出问题了。
设计流程
监控队列长度
- 步骤 1:持续采集队列长度、消费速率和单条消息的处理耗时
- 步骤 2:按队列长度分档设置告警——堆积变慢时预警,堆到上限时立即告警
- 步骤 3:堆积持续增长时触发限流或降级,保护外部 API 不被积压流量二次冲击
关注点:堆积速度与消费速度的差值、分级告警与堆积触发的保护动作。
上线前多想一步
- 队列最多能堆多少? 堆积不是越多越好,超过某个数量就应该限流或降级。
- 任务失败重试几次? 一直重试会拖垮队列,重试完还失败要进入人工可查的列表。
- 用户怎么看进度? 异步处理后,页面要能告诉用户“处理中、成功、失败”,不能只让用户等。
用户请求
GET https://dashboard.kuaiyizhi.cn/api/v4/weather?city=北京Nginx 负载均衡
Nginx
Public API Gateway
Admin API
Admin Dashboard
MySQL
Redis 集群 + 哨兵
MySQL 分层集群
消息队列
离线数仓
发现内容有问题?
告诉我们哪里需要改进。
