前言
面试官问你,当一个电商系统,在一个特定场景(比如秒杀活动,预购活动)中产生大量订单,这些订单在规定时间内未完成支付,你该如何优雅的取消它们。
如果你只是回答定时任务+轮询的方式去修改订单状态,那这个订单还涉及到商品库存、优惠政策,会不会对数据库产生压力?
然后你说在订单生成的时候,在延迟队列中生成消息,超过有效时间由消费端控制事务去处理超时订单,确实这个方案可以减轻数据库压力,但如果是上亿级别的订单,在一个阶段内全部堆积在延迟队列中,岂不是会把队列塞爆了,要知道就算是Kafka的性能也只是百万级别的TPS。
TOC(Timeout-Center 超时中心)设计思想
那今天我们来简单探讨下阿里的TOC架构系统。
整体架构分层
TOC架构总体分为六层,每一层都是专司其职:
- 接入层:统一接收超时任务请求
- 规则层:动态解析超时规则、计算超时时间
- 调度层:核心分桶扫描、分片分配、任务触发
- 执行层:任务下发、异步执行、重试、结果回调
- 存储层:任务数据、规则数据、执行日志(分库分表)
- 运维层:监控、告警、手动干预、故障复盘
为什么TOC能处理上亿订单
核心优势:将全量扫描 转换为精准扫描 ,通过时间分桶+分布式分片的组合将海量任务分散到多个节点处理,有效避免单点压力。
核心机制探讨
时间分桶:从"全表扫描"到"精准扫描"
多级分桶策略(颗粒度可配置):
多级时间分桶策略 ,主要是为了避免全表扫描,分散系统压力,提升超时任务处理效率。
时间分桶核心原理
多级分桶策略设计
TOC采用四级时间分桶体系,从粗到细以此为:
- 天桶:粒度为天(如
20251218) - 小时桶:粒度为小时(如
2025121810) - 分钟桶:粒度为分钟(如
202512181030) - 秒桶:粒度为秒(如
20251218103033)
工作流程:
- 订单创建时,计算超时时间(如创建时间+30分钟)
- 根据超时时间,将订单归入对应粒度的时间桶
- 调度层仅扫描当前时间桶的任务,而非全表
- 任务执行后,根据状态更新或移入更细粒度的桶
时间桶实现细节
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为每个超市任务定义完整的生命周期,通过状态机控制流转:
核心状态控制:
- 待调度:任务已创建,未到超时时间
- 调度中:调度层已拾取任务,准备下发
- 执行中:执行层正在处理关单等逻辑
- 重试中:执行失败,进入阶梯式重试(1min->5min->10min->30min,最多5次)
- 死信:重试多次失败,进入人工介入流程
- 暂停/恢复:支持手动暂停(如用户申请延长付款)、恢复任务
规则引擎:支持复杂业务场景
TOC内置规则引擎,而非固定超时时间:
- 固定超时:如30分钟未支付关单(创建时间+30min)
- 阶梯超时:如1小时未接单 -> 派单,2小时未派单 -> 取消
- 条件超时:如预售订单超时时间=支付截止时间(可动态修改)
- 排除规则:如节假日自动延长超时时间(无需修改代码)
规则配置后实时生效,无需重启服务,支持按业务线/场景灰度发布。 博主会单开一个章节详细讲解规则引擎的架构 、设计等细节。
重试与兜底机制:确保"任务必达"
- 阶梯式重试:执行失败的任务,按"短间隔 -> 长间隔"重试(避免频繁重试压垮业务系统)
- 兜底扫描:调度层除了扫描"当前时间桶",还会定期扫描"历史桶"(如过去1小时),兜底处理漏扫/执行失败的任务
- 死信兜底:死信任务进入专门的死信表,由运维平台告警,支持手动重试/批量处理
与传统方案对比
| 方案 | 传统延迟队列 | TOC超时中心 |
|---|---|---|
| 处理能力 | 百万级 | 亿级+ |
| 时效性 | 依赖队列延迟精度 | 精准到秒级 |
| 资源消耗 | 高(全量扫描) | 低(精准扫描) |
| 灵活性 | 有限 | 支持复杂规则 |
| 可运维性 | 差 | 支持手动干预 |
| 可靠性 | 依赖MQ可靠性 | 多层保障机制 |
实施建议
分阶段实施
- 第一阶段:实现基础时间分桶+状态机,解决核心订单超时问题
- 第二阶段:引入分布式分片,提升系统吞吐量
- 第三阶段:完善规则引擎和运维体系,支持复杂业务场景
关键技术选型
- 时间轮实现:可使用Kafka风格的分层时间轮
- 分布式协调:ZooKeeper或Redis Cluster
- 状态机管理:Spring State Machine或自定义状态机
- 监控体系:Prometheus + Grafana
性能优化技巧
- 缓存热点数据:使用Redis缓存订单状态,减少数据库查询
- 批量处理:对于同一分片的任务进行批量处理,降低数据库压力
- 异步化:将资源回滚操作异步化,提升主流响应速度
- 连接池优化:合理配置Redis连接池,避免频繁创建销毁链接
博主不定时分享技术,博主分享的内容有帮助的可以点点关注 、收藏 、点赞,谢谢您对博主的支持。