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

前言

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

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

然后你说在订单生成的时候,在延迟队列中生成消息,超过有效时间由消费端控制事务去处理超时订单,确实这个方案可以减轻数据库压力,但如果是上亿级别的订单,在一个阶段内全部堆积在延迟队列中,岂不是会把队列塞爆了,要知道就算是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连接池,避免频繁创建销毁链接

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

相关推荐
uzong2 小时前
从单体架构到微服务架构:模式与最佳实践
后端·架构
笨鸟先飞的橘猫2 小时前
广播风暴架构优化方案思考
学习·架构
豹哥学前端2 小时前
10分钟彻底搞懂 window 对象、全局环境与 JS 引擎
前端·面试
探物 AI2 小时前
【感知·医学分割】当 YOLOv11 杀入医学赛道:先检测后分割的级联架构
算法·yolo·计算机视觉·架构
uzong2 小时前
软件架构设计的考虑:如构建一个长生周期的系统
后端·架构
原则猫3 小时前
曝光埋点
架构
白晨并不是很能熬夜4 小时前
【RPC】第 1 篇:全景篇 — 一次 RPC 调用的完整旅程
java·网络·后端·网络协议·面试·rpc·java-zookeeper
apollowing5 小时前
Avalonia UI 12.0.0 正式发布:架构演进和性能飞跃
ui·架构