3. 订单生命周期:订单状态机
订单状态机,说白了就是一笔订单从生到死的完整轨迹。我刚开始做撮合引擎时,觉得这东西不就是「新建→成交」嘛,有啥好设计的?结果第一次上线就出了大问题——部分成交的订单状态乱了,撤单和成交同时发生,数据库里一堆脏数据。嗯,从那以后我再也不敢小看状态机了。
3.1 订单的四种核心状态
我个人习惯把订单状态分成四类,简单明了:
| 状态 | 含义 | 典型场景 |
|---|---|---|
| NEW | 新建 | 用户刚提交订单,还没开始撮合 |
| PARTIAL_FILLED | 部分成交 | 订单被吃掉一部分,还剩一些挂在盘口 |
| FILLED | 完全成交 | 订单全部成交,生命周期结束 |
| CANCELED | 已撤销 | 用户主动撤单,或系统风控强制撤单 |
你可能会问:「那有没有其他状态?比如过期、异常?」
有的系统会加 EXPIRED、REJECTED 等状态。但核心流程里,这四个就够了。多了反而容易乱。
3.2 状态流转图
下面这张 SVG 图,是我自己画的状态流转逻辑。你看一眼就明白了:
这张图我建议你存下来。每次设计状态机时,对着它检查一遍,基本不会漏掉流转路径。
3.3 状态流转的约束规则
状态不是随便跳的。我曾经在项目中遇到过一个问题:用户撤单请求和撮合成交同时到达,结果订单先被标记为 CANCELED,然后又变成了 FILLED。你说这算成交还是没成交?
所以,必须加约束:
- NEW → 只能到 PARTIAL_FILLED、FILLED 或 CANCELED
- PARTIAL_FILLED → 只能到 FILLED 或 CANCELED
- FILLED → 终态,不能再变
- CANCELED → 终态,不能再变
核心原则:一旦进入终态(FILLED 或 CANCELED),任何操作都不能再修改订单状态。这是保证数据一致性的底线。
3.4 代码实现示例
下面是我常用的状态机实现方式,用枚举 + 状态转移表:
// 订单状态枚举
public enum OrderStatus {
NEW,
PARTIAL_FILLED,
FILLED,
CANCELED
}
// 状态转移表
private static final Map<OrderStatus, Set<OrderStatus>> TRANSITIONS = new HashMap<>();
static {
TRANSITIONS.put(OrderStatus.NEW, Set.of(
OrderStatus.PARTIAL_FILLED,
OrderStatus.FILLED,
OrderStatus.CANCELED
));
TRANSITIONS.put(OrderStatus.PARTIAL_FILLED, Set.of(
OrderStatus.FILLED,
OrderStatus.CANCELED
));
TRANSITIONS.put(OrderStatus.FILLED, Collections.emptySet());
TRANSITIONS.put(OrderStatus.CANCELED, Collections.emptySet());
}
// 状态变更方法
public boolean transitionTo(OrderStatus newStatus) {
if (TRANSITIONS.get(this.status).contains(newStatus)) {
this.status = newStatus;
return true;
}
// 我曾经在这里加过告警日志,线上排查问题特别好用
log.warn("非法状态转移: {} -> {}", this.status, newStatus);
return false;
}
避坑指南:我曾经在状态变更时没加乐观锁,结果并发场景下同一笔订单被撮合了两次。后来在数据库里加了 version 字段,每次更新时检查 version,才彻底解决。
3.5 状态机设计要点
总结几个我踩过的坑,你直接拿来用:
- 状态变更必须原子化——用数据库事务或分布式锁,别让状态和实际成交数量不一致
- 记录状态变更日志——我习惯把每次状态变化都写到一张 order_status_log 表里,排查问题时直接看日志,不用猜
- 终态不可逆——FILLED 和 CANCELED 就是终点,任何业务逻辑都不能再改
- 考虑并发场景——撮合引擎是高并发系统,状态机必须能处理同时到来的成交和撤单请求
特别注意:部分成交状态下撤单,不是把整个订单都撤销,而是撤销剩余未成交的部分。已成交的部分不能动。这个逻辑很多新手会搞错。
好了,订单状态机就讲到这里。你想想看,其实核心就是四个状态、几条流转规则。但就是这看似简单的东西,决定了整个撮合引擎的数据一致性。我见过太多系统因为状态机设计不严谨,导致对账不平、资金损失。所以,别嫌我啰嗦,这块真的值得你花时间好好打磨。