轮询设计

最简单的方案

还记得我一开始是怎么实现订单超时取消的吗?

然后用 Linux 的 cron 定时任务,每分钟执行一次:

验证要点

  • 命令只用于验证系统状态,读者不需要记具体参数。

这个方案简单粗暴,但真的能用吗?


数据库轮询的工作原理

核心思想

轮询方案的核心:

1. 定期(如每分钟)查询数据库
2. 找出应该执行的任务(执行时间 <= 当前时间)
3. 执行任务
4. 标记任务为已完成

基本实现


数据库表设计

基本表结构

数据设计要点

  • 核心是在 delay_tasks 里保存业务事实,而不是把规则散落在应用逻辑里。
  • 索引服务于高频查询,重点是缩小扫描范围,而不是堆更多字段。
  • 关键字段包括 idtask_idtask_typetask_dataexecute_atretry_countmax_retrieserror_message,它们决定后续查询和管理能力。

字段说明

字段类型说明
task_idVARCHAR(100)任务唯一标识,用于取消、查询
task_typeVARCHAR(50)任务类型,用于路由到不同的处理器
task_dataTEXT任务数据(JSON 格式)
execute_atTIMESTAMP任务执行时间
statusENUM任务状态
retry_countINT重试次数
max_retriesINT最大重试次数
error_messageTEXT错误信息
consumer_idVARCHAR(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:触发通知

方案对比:

方案减少空查询复杂度适用场景
预估时间⭐⭐⭐⭐⭐⭐⭐任务稀疏
使用缓存⭐⭐⭐⭐频繁查询
触发通知⭐⭐⭐⭐⭐⭐⭐⭐单消费者