这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
轮询设计
最简单的方案
还记得我一开始是怎么实现订单超时取消的吗?
然后用 Linux 的 cron 定时任务,每分钟执行一次:
验证要点
- 命令只用于验证系统状态,读者不需要记具体参数。
这个方案简单粗暴,但真的能用吗?
数据库轮询的工作原理
核心思想
轮询方案的核心:
1. 定期(如每分钟)查询数据库
2. 找出应该执行的任务(执行时间 <= 当前时间)
3. 执行任务
4. 标记任务为已完成基本实现
数据库表设计
基本表结构
数据设计要点
- 核心是在
delay_tasks里保存业务事实,而不是把规则散落在应用逻辑里。- 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
- 关键字段包括
id、task_id、task_type、task_data、execute_at、retry_count、max_retries、error_message,它们决定后续查询和管理能力。
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
task_id | VARCHAR(100) | 任务唯一标识,用于取消、查询 |
task_type | VARCHAR(50) | 任务类型,用于路由到不同的处理器 |
task_data | TEXT | 任务数据(JSON 格式) |
execute_at | TIMESTAMP | 任务执行时间 |
status | ENUM | 任务状态 |
retry_count | INT | 重试次数 |
max_retries | INT | 最大重试次数 |
error_message | TEXT | 错误信息 |
consumer_id | VARCHAR(100) | 消费者 ID(用于故障恢复) |
关键索引
数据设计要点
- 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
为什么这个索引很重要?
数据设计要点
- 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
- 查询目标是快速定位状态、任务或资源,避免在关键路径上做大范围扫描。
轮询间隔的选择
间隔对精度的影响
| 间隔 | 精度 | CPU 消耗 | 数据库压力 | 适用场景 |
|---|---|---|---|---|
| 1 秒 | ±0.5 秒 | 🔴 极高 | 🔴 极高 | 秒杀倒计时 |
| 10 秒 | ±5 秒 | 🟡 高 | 🟡 高 | 高精度场景 |
| 1 分钟 | ±30 秒 | 🟢 中 | 🟢 中 | 订单超时 |
| 5 分钟 | ±2.5 分钟 | 🟢 低 | 🟢 低 | 定时任务 |
| 1 小时 | ±30 分钟 | 🟢 极低 | 🟢 低 | 长周期任务 |
自适应轮询
根据任务量动态调整轮询间隔:
优缺点分析
优点
| 优点 | 说明 |
|---|---|
| ✅ 简单可靠 | 实现简单,容易理解和维护 |
| ✅ 持久化保证 | 数据库天然持久化,任务不会丢 |
| ✅ 事务支持 | 可以利用数据库事务保证一致性 |
| ✅ 易扩展 | 可以增加索引、分区、分表 |
| ✅ 查询灵活 | 支持复杂查询和统计 |
缺点
| 缺点 | 说明 | 影响 |
|---|---|---|
| ❌ 精度有限 | 轮询间隔决定精度 | 不适合高精度场景 |
| ❌ 数据库压力 | 频繁查询数据库 | 高并发时压力大 |
| ❌ 空轮询 | 没有任务时也会查询 | 浪费资源 |
| ❌ 性能瓶颈 | 数据库 I/O 限制 | 无法支撑超高并发 |
想一想
思考 1
如何减少数据库轮询的空查询(没有任务时的查询)?
参考答案
问题分析:
空查询是指轮询时没有找到任何到期任务,但仍然执行了数据库查询。
空查询的代价:
- 数据库连接占用
- 网络往返延迟
- 数据库 CPU 和 I/O 开销
- 日志记录
假设每天 100 万任务,每分钟轮询一次:
- 总轮询次数:1440 次/天
- 如果大部分时间没有任务,90% 是空查询
- 浪费:1296 次无效查询解决方案:
方案 1:预估下次查询时间
方案 2:使用缓存
方案 3:触发通知
方案对比:
| 方案 | 减少空查询 | 复杂度 | 适用场景 |
|---|---|---|---|
| 预估时间 | ⭐⭐⭐⭐⭐ | ⭐⭐ | 任务稀疏 |
| 使用缓存 | ⭐⭐⭐⭐ | ⭐ | 频繁查询 |
| 触发通知 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 单消费者 |