news 2026/5/8 6:09:32

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

作者头像

张小明

前端开发工程师

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/5/7 21:16:34

EmotiVoice在客服机器人中的应用潜力分析

EmotiVoice在客服机器人中的应用潜力分析 在客户服务领域&#xff0c;一次通话的语气往往比内容本身更能决定用户的满意度。当用户焦急地询问订单状态时&#xff0c;一句冷冰冰的“系统显示正常”可能激化情绪&#xff0c;而同样的信息如果以温和关切的语调说出&#xff0c;反而…

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

Python语言之数据结构操作对比:字典、列表、元组、集合

Python数据结构操作对比&#xff1a;字典、列表、元组、集合 以下是四种主要数据结构的操作对比&#xff0c;包含详细示例和注释&#xff1a; 1. 创建&#xff08;初始化&#xff09; # 字典 (dict) # 创建空字典 dict1 {} dict2 dict() # 创建带初始值的字典 dict3 …

作者头像 李华
网站建设 2026/5/6 3:24:44

Python语言之OS模块各部将简单介绍

os 模块是 Python 标准库中最强大、功能最丰富的模块之一&#xff0c;除了 os.path 外&#xff0c;它主要包含以下几大类功能&#xff1a; 1. 文件和目录操作 import os# 创建和删除目录 os.mkdir(new_dir) # 创建单个目录 os.makedirs(dir1/dir2/dir3) # 递…

作者头像 李华
网站建设 2026/5/8 2:08:17

云端算力 云手机 巨 椰

云端算力是指通过云计算技术&#xff0c;将分散在多个服务器上的计算资源整合起来&#xff0c;为用户提供强大计算能力的服务&#xff0c;用户可按需获取和使用这些算力&#xff0c;无需自行搭建和维护硬件设施。云手机则是依托云端算力与存储资源&#xff0c;将手机的核心计算…

作者头像 李华
网站建设 2026/5/6 19:54:43

基于springboot口腔医院信息管理系统

基于Spring Boot的口腔医院信息管理系统是一个高效、安全且易于使用的工具&#xff0c;专为口腔医院设计&#xff0c;以提高管理效率和服务质量。以下是对该系统的详细介绍&#xff1a; 一、系统概述 该系统以Spring Boot框架为基础&#xff0c;结合前端技术&#xff08;如Vue、…

作者头像 李华