1. 别急着上消息队列:先搞清楚自己的系统到底缺什么
先聊聊标题里那个排序:能用数据库轮询解决的不用Redis,能用Redis解决的不用消息队列。
这句话不是教条,是我这几年处理订单系统真实踩坑后最想分享的一条准则。很多团队一提到订单状态同步、任务调度、通知推送,第一反应就是上MQ,仿佛没有消息队列的项目就不够“互联网”。但现实情况是,消息队列引入之后带来的运维成本、数据一致性成本、排查链路复杂度,往往比它解决的问题还要多。
你得先问自己一个问题:你的系统,到底需要一个“实时推送”方案,还是只需要一个“轮询拉取”方案?这两者的差距天壤之别。
实时推送方案,典型就是消息队列。生产者把消息往队列里一塞,消费者立即收到,端到端延迟能做到毫秒级。但为了这个“毫秒级”,你需要解决的问题一个接一个:消息不丢失怎么搞、重复消费怎么处理、消费失败重试链路怎么设计、积压了怎么扩容、顺序性怎么保障。每一个问题都是一个深坑,每一个坑都需要对应的人力成本去填。
轮询拉取方案,说白了就是定时去查数据,有变化就处理,没变化就歇着。这个方案不需要额外引入任何中间件——数据库就在那儿,Redis已经用了的话也顺手,查询条件写上,定时任务一把梭。缺点是轮询间隔意味着延迟,但在大量真实业务场景下,这个延迟压根不是瓶颈。
举个例子,后端订单超时未支付自动关闭。支付网关回调的时效性再慢,一般也就几十秒内;而订单关闭这个操作,用户感知层面的容忍度是以“分钟”计的。你用数据库定时任务每30秒扫一次,只要索引建对了,对数据库的压力也没想象中大。但你非要用消息队列来做延迟队列,就要处理Redis过期监听不可靠的问题,或者引入RocketMQ的延迟消息等级——为了省那几十秒延迟,结果捅出一堆新问题,这个账怎么算都不划算。
再举个例子,订单创建后要给用户推送站内信、短信、微信模板消息。这种场景,数据库里加一张message_task表,状态字段从PENDING到SENT,定时任务按批次扫,扫到就发,发完就更新状态。就这么简单。我之前做过一个每秒峰值不到300笔订单的小型交易系统,用的就是这种方案。消息积压量最大也就两万条,但发送能力完全够用,数据还能随时查、随时对账,出了问题直接看数据库表,排查成本低到几乎没有。
所以,选轮询方案还是消息队列,本质上要看需求的延迟敏感度和数据规模。如果一个功能允许秒级甚至分钟级延迟,数据库轮询就是答案;如果Redis的重试、幂等、查询能力能兜住,那也不要轻易上MQ。只有当你发现一个功能确实需要毫秒级实时性、同时数据量又大到数据库扛不住定时扫描的时候,引入消息队列才是一个合理的选择。
这个顺序就是标题要表达的核心思想:技术选型要从小到大地去考虑,而不是从大到小地去做架构储备。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库轮询的实际边界:什么时候该用它、什么时候该撤
2.1 数据库轮询的本质是一张“待办表”加一个“调度器”
很多人觉得数据库轮询很low、很笨,其实是因为没把它当产品来设计。数据库轮询的核心就两件事:一张记录任务状态的表,一个按固定频率扫描这张表的调度器。
这张任务表设计得好,整个轮询机制就是可靠的。字段大概长这样:
sql复制CREATE TABLE order_task (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL COMMENT '订单号',
task_type TINYINT NOT NULL COMMENT '任务类型:1-超时关单 2-推送通知 3-自动确认收货',
task_status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-待处理 1-处理中 2-成功 3-失败',
retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
next_execute_time DATETIME NOT NULL COMMENT '下次执行时间',
create_time DATETIME NO
