一文搞懂 Spring Task、Redis 定时消息与 MQ 消息队列:任务通知与消息通知的三大实现方案
写在前面我的第一个项目里用Scheduled写了一行代码每天凌晨清理过期订单。当时觉得定时任务真简单。后来学到 Redis发现它也能做延迟消息比如 30 分钟后自动取消未支付订单但因为项目没需求一直没用过。再后来微服务架构下用 RabbitMQ 处理异步通知、削峰填谷才体会到消息队列的威力。这三者——Spring Task、Redis 的消息通知能力、MQ 消息队列听起来都能“在某个时间做某件事”或“通知其他服务”但它们的设计目标、适用场景和优缺点截然不同。很多开发者会混淆什么时候用定时轮询什么时候用 Redis 过期回调什么时候上 MQ这篇笔记我们从“任务通知”和“消息通知”两个维度把这三个方案放在一起对比帮你理清选型思路。1️⃣ 任务通知 vs 消息通知明确概念2️⃣ Spring Task最轻量的定时任务方案实现方式Spring 内置的定时任务框架基于Scheduled注解。Component public class OrderCleanTask { // 每天凌晨2点执行 Scheduled(cron 0 0 2 * * ?) public void cleanExpiredOrders() { // 查询过期订单并删除 } // 固定延迟5秒上次执行结束后等待5秒 Scheduled(fixedDelay 5000) public void doSomething() {} // 固定速率5秒不管上次是否完成间隔5秒 Scheduled(fixedRate 5000) public void heartbeat() {} }开启支持EnableScheduling作用与优缺点优点无额外依赖Spring 自带配置简单cron 表达式灵活适合对实时性要求不高的周期性任务缺点不支持分布式协调多实例同时执行会重复不支持失败重试、死信无法做精确的延迟消息只能靠轮询浪费资源默认单线程多任务相互阻塞可配置线程池改进Spring Task 分布式锁ShedLock可解决重复执行问题。3️⃣ Redis 定时消息轻量级的延迟与发布/订阅Redis 实现“定时/消息通知”有两种常见方式方式一键空间通知Key‑space Notifications—— 延迟消息利用 Redis 的过期事件当一个 key 过期时Redis 会发布一个__keyevent0__:expired消息。步骤注意Redis 5.0 后的Stream类型大幅提升了可靠性消费者组、ACK可当作轻量 MQ 使用。4️⃣ MQ 消息队列可靠异步通信的工业级方案开启键空间通知CONFIG SET notify-keyspace-events Ex在应用里订阅过期事件频道将待延迟处理的任务作为 key 存入 Redis设置过期时间即延迟时间// 发送延迟消息30分钟后取消订单 redisTemplate.opsForValue().set(order:cancel:123, 需要取消, 30, TimeUnit.MINUTES); // 监听过期事件 Component public class RedisKeyExpiredListener extends KeyExpirationEventMessageListener { Override public void onMessage(Message message, byte[] pattern) { String expiredKey message.toString(); if (expiredKey.startsWith(order:cancel:)) { String orderId extractOrderId(expiredKey); cancelOrder(orderId); } } }方式二Sorted Set 实现定时队列使用ZADD将任务按时间戳排序由定时轮询取出到期的任务。// 添加延迟任务score 执行时间戳 redisTemplate.opsForZSet().add(delay:queue, taskId, executeTime); // 轮询可用 Spring Task 每隔1秒扫描 SetString tasks redisTemplate.opsForZSet().rangeByScore(delay:queue, 0, now);方式三Pub/Sub 实时消息通知Redis 的PUBLISH/SUBSCRIBE可以实现简单的实时消息广播。优缺点总结总体评价优点轻量依赖 Redis通常已有性能高缺点可靠性低尤其过期事件在 Redis 重启后会丢失没有消费确认、重试、死信等机制不适合核心业务以 RabbitMQ、RocketMQ、Kafka 为代表。实现延迟消息以 RabbitMQ 为例// 使用插件 rabbitmq-delayed-message-exchange 或 死信队列实现 rabbitTemplate.convertAndSend(delay.exchange, routing.key, message, msg - { msg.getMessageProperties().setDelay(30000); // 延迟30秒 return msg; });实现普通异步消息// 发送消息 rabbitTemplate.convertAndSend(order.exchange, order.created, orderEvent); // 消费者 RabbitListener(queues order.queue) public void handleOrderCreated(OrderEvent event) { // 发邮件、积分等 }优缺点总结优点消息持久化不丢失消费确认机制可靠处理支持复杂路由、顺序消息、事务消息天然分布式支持集群缺点引入额外组件运维成本高学习曲线较陡5️⃣ 三大方案横向对比6️⃣ 组合使用场景取长补短实际项目中往往混合使用个人建议如果是单体项目、非核心逻辑Spring Task Redis 足够微服务、核心业务直接上 MQ别为了省事而妥协可靠性。7️⃣ 总结与选型建议一句话选型周期性定时任务→ Spring Task可容忍少量丢失的延迟消息→ Redis 键空间通知 / Sorted Set高可靠、高吞吐异步通信→ MQ既要有延迟又要有 ACK→ Redis Stream折中或 MQ推荐记忆口诀简单定时 Spring Task轻量延迟用 Redis 过期可靠异步上 MQ。如果要实现一个“订单支付成功后延迟 10 分钟给用户发送评价提醒”的功能你会选择哪种方案为什么如果要求消息绝对不能丢失又该如何做欢迎在评论区分享你的设计和理由