线程栈天然隔离使订单状态机无需加锁即可串行执行。通过协程绑定订单生命周期,状态迁移设计为纯函数式、无副作用的有限状态自动机,共享资源访问收口至原子提交阶段,异常回滚在协程栈内闭环处理。
线程栈本身不共享、天然隔离,这是重构订单状态机最被忽视的底层优势。不用加锁、不依赖原子变量,就能让每个订单的状态流转在自己的栈空间里串行执行——关键在于把“状态变更”从共享内存操作,转为单线程上下文内的纯函数式推进。
用协程绑定订单生命周期
每个订单请求启动一个独立协程(如 PHP 的 Swoole 协程、Java 虚拟线程、Go goroutine),整个状态机逻辑(校验→锁库存→创建→扣减→通知)都在该协程栈内顺序执行。协程调度器保证同一时间只有一个协程在操作本订单的数据,无需对订单对象加锁。
- PHP 示例:Swoole 协程中直接调用 $order->transition('check') → $order->transition('reserve') → $order->transition('confirm')
- Java 示例:虚拟线程内用 switch-case 或状态枚举驱动,所有字段更新仅作用于局部 Order 实例,不暴露给其他线程
- 避免将订单 ID 作为全局 map 的 key 去并发读写,改为协程本地持有引用
状态迁移函数设计为纯计算
每个 transition() 方法只做三件事:读取当前状态、判断是否可迁、返回新状态或错误。不修改任何共享资源,不触发远程调用,不写日志——这些副作用延后到状态确认后再统一处理。
- 例如 checkStock() 只返回 bool 和剩余库存数,不执行 UPDATE;reserve() 只构造待提交的扣减指令,不直连数据库
- 状态对象用不可变结构(如 record 类或 frozen dict),每次迁移生成新实例,旧实例自动失效
- 这样整个状态机变成无副作用的有限状态自动机(FSM),可单元测试、可回放、可快照
共享资源访问收口到“提交阶段”
协程内完成状态校验和决策后,进入原子提交阶段。此时才批量操作外部系统,且通过预占位+最终一致性保障正确性。
- 库存扣减走 Redis Lua 脚本(单命令完成判断+扣减),避免先查后更导致超卖
- 订单主表插入用数据库 INSERT ... ON CONFLICT DO NOTHING,靠唯一约束防重
- 消息投递走异步通道(如 Swoole Channel 或 Java VirtualThread + BlockingQueue),由专用 worker 进程/线程统一发 MQ
异常与回滚在栈内闭环
协程栈崩溃时,未提交的状态自然丢弃;若需补偿,也在同一线程内触发逆向操作,不跨协程通信。
- 使用 defer / try-with-resources / defer func() 模式,在协程退出前检查状态,自动触发 cancel 或 refund
- 例如 reserve 成功但 confirm 失败,则在 defer 中调用 undoReserve(),仍走 Lua 脚本恢复库存
- 全程不依赖分布式事务或 TCC,因所有动作都发生在单次协程执行上下文中
本质是把“订单状态机”从多线程争抢的共享模型,拉回到单线程确定性执行的函数式模型。栈即上下文,协程即生命周期容器,状态流转回归本质——顺序、可控、可推演。











