news 2026/6/4 23:57:55

如何设计分布式延时消息?——以机票购买场景为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何设计分布式延时消息?——以机票购买场景为例

前言

在真实业务中,“延时触发”是一类非常常见但又容易被低估的需求,例如:

  • 机票下单后15 分钟未支付自动取消
  • 订单创建后30 分钟关闭
  • 活动开始前定时推送通知
  • 资源锁定一段时间后自动释放

单机系统中,这类需求实现并不复杂;
但在分布式、高并发、可扩展系统中,延时消息的设计就变得非常关键。

本文将以「购买机票超时未支付自动取消订单」为例,循序渐进讲清楚:

  • 本地延时是如何实现的
  • 本地方案的局限在哪里
  • 分布式延时消息的几种主流设计方案
  • 业界(RocketMQ)是如何解决延时消息问题的
  • 一个可落地的分布式延时消息设计思路

业务场景抽象:机票超时未支付

典型业务流程

  1. 用户下单购买机票
  2. 系统创建订单,状态为「待支付
  3. 系统需要在15 分钟后检查订单
  • 如果已支付 → 不处理

  • 如果未支付 → 自动取消订单,释放座位

这本质上是一个:

“现在 + 延迟时间 → 执行一段逻辑”的问题

本地延时任务的实现方式(单机)

在进入分布式之前,先看最基础的实现方式。

Timer(已不推荐)

Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { cancelOrder(orderId); } }, 15 * 60 * 1000);

问题:

  • 单线程执行
  • 任务异常会影响整个 Timer
  • 无法承载高并发

ScheduledThreadPoolExecutor(推荐)

ScheduledExecutorService executor = Executors.newScheduledThreadPool(4); executor.schedule(() -> { cancelOrder(orderId); }, 15, TimeUnit.MINUTES);

优点:

  • 支持线程池
  • API 简单
  • 本地可靠性较好

本地延时方案的问题

虽然ScheduledThreadPoolExecutor很好用,但只能用于单机,在真实生产环境会遇到:

问题说明
服务重启延时任务直接丢失
集群部署多实例无法协调
扩容缩容任务归属混乱
高并发内存压力大

结论:本地延时 ≠ 分布式延时

从本地延时中抽象可复用的思想

虽然本地方案不可直接用于分布式,但它给了我们重要启发:

延时任务 = 任务 + 触发时间

换句话说,我们只要解决两个问题:

  1. 任务存在哪里

  2. 什么时候被取出来执行

分布式延时消息的核心设计思路

核心目标

  • 可靠存储:服务重启不丢任务
  • 可水平扩展
  • 时间精度可控
  • 高吞吐

分布式延时消息方案一:外部存储 + 定时扫描

设计思路

将延时消息存储在外部系统中:

(orderId, executeTime, payload, status)

然后由后台线程周期性扫描

SELECT * FROM delay_task WHERE execute_time <= now() AND status = 'NEW' LIMIT 100;

架构示意

下单 → 写延时任务表 → 定时扫描 → 执行业务

优缺点分析

优点:

  • 实现简单
  • 可控性强
  • 易于理解

缺点:

  • 扫描数据库压力大
  • 时间精度有限(秒级)
  • 高并发下性能瓶颈明显

适合:中小规模系统

分布式延时消息方案二:Redis 实现

Redis ZSet(推荐)

利用 ZSet 的score表示时间戳:

key: delay:order score: executeTimestamp value: orderId

写入延时任务

ZADD delay:order 1700000000 order123

消费逻辑

ZRANGEBYSCORE delay:order -inf now LIMIT 0 100

取到后:

  • 执行业务
  • ZREM删除任务

优缺点

优点:

  • 性能极高
  • 实现相对简单
  • 天然支持排序

缺点:

  • Redis 内存成本
  • 数据持久性依赖 Redis 配置
  • 需要处理重复消费、幂等

业界使用非常广泛

分布式延时消息方案三:时间轮(Time Wheel)

核心思想

将时间划分为多个“槽位”:

| 0 | 1 | 2 | 3 | 4 | 5 | ... |

每个槽代表一个时间区间,任务被放入对应槽位。

特点

  • 插入和触发复杂度接近 O(1)
  • 非常适合大量延时任务

局限

  • 实现复杂
  • 精度有限
  • 通常需要多级时间轮

Netty、Kafka、RocketMQ 都采用了时间轮思想

业界成熟方案:RocketMQ 延时消息

RocketMQ 的做法

RocketMQ不支持任意时间延时,而是采用:

固定等级延时

例如:

Level延时时间
11s
25s
310s
430s
51m
......

实现原理简述

  • 延时消息写入特殊 Topic
  • 使用时间轮 + 定时调度
  • 到期后转发到真实 Topic

优缺点

优点:

  • 高性能
  • 高可靠
  • 生产级方案

缺点:

  • 延时时间不灵活
  • 强依赖 MQ

一个完整的分布式延时消息落地方案(机票)

推荐组合方案

下单 ↓ 发送延时消息(Redis ZSet / RocketMQ) ↓ 延时到期 ↓ 消费者校验订单状态 ↓ 未支付 → 取消订单

关键设计点

  • 业务幂等
  • 状态二次校验
  • 延时消息 ≠ 定时任务
  • 失败重试机制

总结

方案适用场景
本地延时单机、简单系统
DB 扫描小规模、低频
Redis ZSet高并发、灵活延时
时间轮超大规模
RocketMQ企业级

延时消息的本质不是“等多久”,而是“何时可靠地执行一次”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 14:56:03

《国产数据库技术》学习心得:DM数据库实践之路

在数字化转型加速推进的背景下&#xff0c;国产数据库的重要性日益凸显。本学期通过《国产数据库技术》课程的学习&#xff0c;我重点钻研了达梦数据库&#xff08;DM&#xff09;的核心技术&#xff0c;从环境搭建到实操应用&#xff0c;逐步掌握了其安装配置、备份还原、SQL编…

作者头像 李华
网站建设 2026/6/3 16:13:36

记录线上k8s拉取不了阿里云镜像的一次临时处理

大致背景&#xff1a;本人今天有一个需求要上线&#xff0c;于是部署了2个服务&#xff0c;因为公司用的是k8s阿里云镜像&#xff0c;所以在公司的流程是部署完服务之后用生成的阿里云服务镜像地址去k8s管理平台直接替换对应服务的镜像地址&#xff0c;k8s部署完成即为完成上线…

作者头像 李华
网站建设 2026/6/5 12:01:35

8个降AI率工具,专科生必备避坑指南

8个降AI率工具&#xff0c;专科生必备避坑指南 AI降重工具&#xff1a;专科生论文写作的“隐形助手” 随着人工智能技术在学术领域的广泛应用&#xff0c;越来越多的专科生开始面临一个共同的难题——如何降低论文中的AIGC率&#xff0c;同时保持内容的逻辑性和语义通顺。尤其是…

作者头像 李华
网站建设 2026/6/3 14:33:48

C语言、C++、C#、VB语言对比探究,我们该如何选择?

C语言、C、C#、VB语言对比探究 一、概述 这四种语言代表了编程语言发展的不同阶段和设计哲学&#xff1a; C语言&#xff1a;面向过程的系统级编程语言C&#xff1a;多范式语言&#xff0c;支持面向过程和面向对象C#&#xff1a;完全面向对象的现代编程语言VB&#xff1a;基于.…

作者头像 李华
网站建设 2026/6/4 1:49:41

高性价比云手机 多端同步

云手机是基于端云一体虚拟化技术&#xff0c;将手机的核心计算、存储功能迁移至云端服务器的 “虚拟手机”&#xff0c;它通过在服务器上构建独立手机操作系统实例&#xff0c;用户可通过普通终端远程访问和操控&#xff0c;无需消耗过多本地硬件资源。云手机依托云端的计算和存…

作者头像 李华