RabbitMQ 削峰:百万级写入如何平稳化
招聘平台有个典型的"潮汐流量"问题:早上 9 点到 11 点是求职高峰,投递请求瞬时量是平峰的 10 倍。数据库直写的话,要么连接池被打爆,要么主从延迟飙升。
一、为什么必须削峰
直写链路:API → 业务校验 → 事务写库。问题在于:
- 峰值并发超出库的承受上限:单库连接池 200,峰值 5000 QPS 涌入,全部排队等连接
- 事务放大:一次投递要写投递单 + 更新职位计数 + 写简历快照,一个请求 5+ 次写
- 雪崩风险:DB 慢 → 连接占满 → API 线程池耗尽 → 整站不可用
削峰的本质:把「瞬时 5000 QPS」变成「每秒恒定 800 的处理能力」,用队列做缓冲。
二、方案:RabbitMQ 异步削峰
关键配置
|
|
- prefetch=200:消费端同时处理 200 条,控制并发写库
- 手动 ack:处理成功才确认,失败重回队列
- 死信队列:重试 N 次仍失败进 DLX,人工兜底
削峰效果
| 指标 | 直写 | RabbitMQ 削峰 |
|---|---|---|
| API 响应 | 峰值时 2~5s 超时 | 稳定 < 100ms |
| 峰值写库 QPS | 5000(打爆) | 恒定 800 |
| 高峰丢单 | 偶发超时丢 | 0(消息可靠) |
三、削峰方案的三个坑
坑 1:消息堆积 = 用户感知延迟
削峰意味着投递不是"立即生效"。早上高峰,用户投完简历如果 30 秒后才看到"已投递",体验崩。
解法:双通道。 用户可感知的结果(投递状态)走同步直写(轻量),重量级的衍生写(简历快照、计数、通知)走异步队列。削的是"重"的,不削"轻"的。
坑 2:消费端不幂等 = 重复写
手动 ack 重试、消费端重启,都会造成消息重复投递。
解法:业务幂等键。 消息体带 delivery_id,消费时先查重(唯一索引/Redis SETNX),已处理直接 ack。
坑 3:消费能力跟不上积压
消费服务挂了 10 分钟,积压 100 万条,恢复后 prefetch 冲一波把库又打爆。
解法:渐进恢复。 消费启动时前 60 秒限速(prefetch 从 20 逐步升到 200),让 DB 连接池和缓冲池慢慢热身。
四、监控与兜底
- 队列深度监控:
rabbitmq_queues_messages告警阈值 5 万,超了自动扩容消费实例 - 消费延迟:用消息时间戳算
now - enqueue_time,> 1 分钟告警 - 死信兜底:DLX 队列每天巡检,重放或人工处理
总结
削峰不是把问题藏起来,是把瞬时压力换成可控延迟:
- 分清轻重:用户可感知的同步,重的衍生写异步
- 队列要配限流:prefetch + 手动 ack,否则队列只是把压力搬到消费端
- 消费必须幂等:重复消息是异步系统的常态,不是异常
记住:队列不解决并发问题,队列解决的是"并发来了你扛不住"的问题——真正扛住的是恒定可控的消费速度。