这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
可靠性
那次惨痛的教训
2024 年 4 月 1 日(愚人节,但对我们来说不是玩笑),我们的延时队列服务发生了一起严重事故。
事故经过
时间线:
14:00 - 用户 A 下单,创建 30 分钟后取消订单的任务
14:05 - 用户 A 支付成功,取消超时任务
14:30 - 延时队列服务重启(部署新版本)
14:31 - 用户 A 收到订单被取消的通知 🤯根因分析
经过排查,问题出在这里:
问题根源:
- ❌ 任务只存储在内存,无持久化
- ❌ 服务重启后,任务全部丢失
- ❌ 取消任务只是从内存删除,没有记录
- ❌ 重启后,任务不存在,但用户的期望还在
可靠性的三个维度
1. 任务不丢失(No Loss)
任务创建后,无论发生什么情况(服务器崩溃、网络故障、停电),都不能丢失。
2. 任务不重复(No Duplicate)
同一个任务不能被多次执行。如果执行失败需要重试,要有明确的控制。
3. 任务不遗漏(No Miss)
到期的任务必须被执行,不能因为任何原因被遗漏。
可靠性三角
不丢失
/ \
/ \
/_____\
不遗漏 不重复任务不丢失的保障
方案 1:双层存储(数据库 + 内存)
双层存储的优势:
| 层级 | 存储 | 优点 | 缺点 |
|---|---|---|---|
| L1: 数据库 | 磁盘 | ✅ 强持久化 ✅ 事务保证 ✅ 可靠性高 | ⚠️ 性能较低 |
| L2: Redis | 内存 | ✅ 高性能 ✅ 快速查询 | ⚠️ 需要持久化配置 |
方案 2:WAL(Write-Ahead Log)
使用写前日志确保数据不丢失:
方案 3:消息队列持久化
使用 Kafka 等持久化消息队列:
任务不重复的保障
幂等性设计
确保同一个任务即使被多次调用,结果也是一样的。
三层幂等检查
任务不遗漏的保障
补偿机制
定期检查是否有遗漏的任务:
心跳机制
消费者定期发送心跳,证明自己还活着:
想一想
思考 1
如果消费者在执行任务时挂了,如何保证任务能被重新执行?
参考答案
问题分析:
消费者在执行任务时挂了,会导致:
- 任务状态停留在
processing - 任务没有完成
- 其他消费者不知道该任务需要重试
解决方案:心跳 + 超时检测
关键要点:
- ✅ 记录心跳时间:
heartbeat_at字段 - ✅ 定期检测死任务:心跳超时 > 30 秒
- ✅ 重置任务状态:从
processing改回pending - ✅ 增加重试次数:记录失败次数