如何取消大批量的超时订单,关于超时架构的探讨

前言

面试官问你,当一个电商系统,在一个特定场景(比如秒杀活动,预购活动)中产生大量订单,这些订单在规定时间内未完成支付,你该如何优雅的取消它们。

如果你只是回答定时任务+轮询的方式去修改订单状态,那这个订单还涉及到商品库存、优惠政策,会不会对数据库产生压力?

然后你说在订单生成的时候,在延迟队列中生成消息,超过有效时间由消费端控制事务去处理超时订单,确实这个方案可以减轻数据库压力,但如果是上亿级别的订单,在一个阶段内全部堆积在延迟队列中,岂不是会把队列塞爆了,要知道就算是Kafka的性能也只是百万级别的TPS。

TOC(Timeout-Center 超时中心)设计思想

那今天我们来简单探讨下阿里的TOC架构系统。

整体架构分层

TOC架构总体分为六层,每一层都是专司其职:

flowchart LR id1{{接入层}} --> id2(规则层) --> id3(调度层) --> id4(执行层) --> id5[(存储层)] --> id6(((运维层)))
  • 接入层:统一接收超时任务请求
  • 规则层:动态解析超时规则、计算超时时间
  • 调度层:核心分桶扫描、分片分配、任务触发
  • 执行层:任务下发、异步执行、重试、结果回调
  • 存储层:任务数据、规则数据、执行日志(分库分表)
  • 运维层:监控、告警、手动干预、故障复盘

为什么TOC能处理上亿订单

核心优势:将全量扫描 转换为精准扫描 ,通过时间分桶+分布式分片的组合将海量任务分散到多个节点处理,有效避免单点压力。

核心机制探讨

时间分桶:从"全表扫描"到"精准扫描"

多级分桶策略(颗粒度可配置):

flowchart LR A["天桶(2025-12-18)"] B["小时桶(10点)"] C["分钟桶(30分)"] D["秒桶(38秒)"] A --> B --> C --> D

多级时间分桶策略 ,主要是为了避免全表扫描,分散系统压力,提升超时任务处理效率

时间分桶核心原理

多级分桶策略设计

TOC采用四级时间分桶体系,从粗到细以此为:

  • 天桶:粒度为天(如20251218
  • 小时桶:粒度为小时(如2025121810
  • 分钟桶:粒度为分钟(如202512181030
  • 秒桶:粒度为秒(如20251218103033

工作流程

  1. 订单创建时,计算超时时间(如创建时间+30分钟)
  2. 根据超时时间,将订单归入对应粒度的时间桶
  3. 调度层仅扫描当前时间桶的任务,而非全表
  4. 任务执行后,根据状态更新或移入更细粒度的桶
时间桶实现细节

1. 数据库表结构设计

TOC的核心人物表包含关键字段:

SQL 复制代码
CREATE TABLE task_info (
    task_id BIGINT PRIMARY KEY,
    biz_id VARCHAR(64) COMMENT '订单ID',
    bucket_id BIGINT COMMENT '时间桶ID',
    shard_id INT COMMENT '分片ID',
    status VARCHAR(32) COMMENT '任务状态',
    timeout_time DATETIME COMMENT '超时时间'
);

其中bucket_id是实现时间分桶的核心字段

2. 索引优化

TOC为时间桶查询设计了组合索引:

SQL 复制代码
CREATE INDEX idx_bucket_status_shard ON task_info (bucket_id, status, shard_id);

该索引使调度层额能高效执行以下查询:

SQL 复制代码
-- 扫描2025-12-18 10:30桶、分片38、状态为待调度的任务
SELECT task_id, biz_id, timeout_time
FROM task_info
WHERE bucket_id = 202512181030
AND status = 'INIT'
AND shard_id = 38;

性能对比:全表扫描可能需要秒级/分钟级,而索引扫描仅需要毫秒级(即使单桶有百万级任务)。

时间桶的优化还牵扯到分布式分片 ,以及存储优化等细节,这里不做展开,博主会单独开章节详细说明。

分桶优势

  • 扫描范围极小:仅扫描"当前时间桶"(如10:30:00,只扫10:30桶),而非全表;
  • 峰值分散:不同桶的扫描时间错开(如10:30桶在10:30扫描,10:31桶在10:31扫描),避免集中压力;
  • 灵活扩展:可按业务调整桶颗粒度(核心业务秒桶,非核心分钟桶)

任务生命周期管理:确保"不丢,不重复,可追溯"

TOC为每个超市任务定义完整的生命周期,通过状态机控制流转:

flowchart LR A("待调度") B("调度中") C("执行中") D("重试中") E{{"死信"}} F("暂停中") G("恢复") H{{执行成功}} A --> B --> C --> H A --失败--> D --> E B --失败--> E D --暂停--> F --> G --> A

核心状态控制

  • 待调度:任务已创建,未到超时时间
  • 调度中:调度层已拾取任务,准备下发
  • 执行中:执行层正在处理关单等逻辑
  • 重试中:执行失败,进入阶梯式重试(1min->5min->10min->30min,最多5次)
  • 死信:重试多次失败,进入人工介入流程
  • 暂停/恢复:支持手动暂停(如用户申请延长付款)、恢复任务

规则引擎:支持复杂业务场景

TOC内置规则引擎,而非固定超时时间:

  • 固定超时:如30分钟未支付关单(创建时间+30min)
  • 阶梯超时:如1小时未接单 -> 派单,2小时未派单 -> 取消
  • 条件超时:如预售订单超时时间=支付截止时间(可动态修改)
  • 排除规则:如节假日自动延长超时时间(无需修改代码)

规则配置后实时生效,无需重启服务,支持按业务线/场景灰度发布。 博主会单开一个章节详细讲解规则引擎的架构设计等细节。

重试与兜底机制:确保"任务必达"

  • 阶梯式重试:执行失败的任务,按"短间隔 -> 长间隔"重试(避免频繁重试压垮业务系统)
  • 兜底扫描:调度层除了扫描"当前时间桶",还会定期扫描"历史桶"(如过去1小时),兜底处理漏扫/执行失败的任务
  • 死信兜底:死信任务进入专门的死信表,由运维平台告警,支持手动重试/批量处理

与传统方案对比

方案 传统延迟队列 TOC超时中心
处理能力 百万级 亿级+
时效性 依赖队列延迟精度 精准到秒级
资源消耗 高(全量扫描) 低(精准扫描)
灵活性 有限 支持复杂规则
可运维性 支持手动干预
可靠性 依赖MQ可靠性 多层保障机制

实施建议

分阶段实施

  • 第一阶段:实现基础时间分桶+状态机,解决核心订单超时问题
  • 第二阶段:引入分布式分片,提升系统吞吐量
  • 第三阶段:完善规则引擎和运维体系,支持复杂业务场景

关键技术选型

  • 时间轮实现:可使用Kafka风格的分层时间轮
  • 分布式协调:ZooKeeper或Redis Cluster
  • 状态机管理:Spring State Machine或自定义状态机
  • 监控体系:Prometheus + Grafana

性能优化技巧

  • 缓存热点数据:使用Redis缓存订单状态,减少数据库查询
  • 批量处理:对于同一分片的任务进行批量处理,降低数据库压力
  • 异步化:将资源回滚操作异步化,提升主流响应速度
  • 连接池优化:合理配置Redis连接池,避免频繁创建销毁链接

博主不定时分享技术,博主分享的内容有帮助的可以点点关注收藏点赞,谢谢您对博主的支持。

相关推荐
fake_ss1985 小时前
AI时代学习全栈项目开发的新范式
java·人工智能·学习·架构·个人开发·学习方法
米高梅狮子6 小时前
第2章 docker容器
运维·docker·云原生·容器·架构·kubernetes·自动化
Aphasia3118 小时前
CORS、CSRF和XSS
面试
木斯佳8 小时前
前端八股文面经大全:腾讯WXG暑期前端一面(2026-05-15)·面经深度解析
前端·面试·笔试
微学AI8 小时前
Hermes Agent vs Claude Code 架构对比与创新分析
架构
沪漂阿龙8 小时前
面试题详解:检索链路设计全攻略——RAG 检索架构、查询理解、多路召回、混合检索、Rerank、上下文构造与评估闭环
大数据·人工智能·架构
张元清9 小时前
useEffect 之外:专门处理异步、深比较和 SSR 的 Effect Hook
前端·javascript·面试
码云之上9 小时前
万星入坞·其二:子应用如何优雅地"入坞"
性能优化·架构·前端框架
Apache RocketMQ9 小时前
RocketMQ 源码解析——Controller 高可用切换架构
架构·rocketmq·java-rocketmq
数字化顾问9 小时前
(122页PPT)数字化架构的演进和治理(附下载方式)
java·运维·架构