第四章:执行引擎层设计

低延迟订单处理流水线、原子化操作设计、订单状态机——这三个东西,说白了就是执行引擎的骨架。

我做了这么多年量化系统,见过太多团队在策略层花了大把精力,结果执行层一塌糊涂。订单发不出去、状态对不上、延迟高得离谱。嗯,今天我们就来聊聊这块硬骨头。

4.1 低延迟订单处理流水线

先问个问题:你的订单从策略决策到发往交易所,中间经过了多少步骤?

我见过最夸张的,一个订单要经过七八个模块的序列化、反序列化、队列传递。你想想看,每一步都在吃延迟。高频交易里,微秒级的差距就是盈亏的分水岭。

核心原则:流水线设计要像工厂的传送带,每个工位只做一件事,做完立刻传给下一站。

我个人习惯把订单处理流水线拆成四个阶段:

  1. 接收阶段——从策略层拿到原始订单指令
  2. 校验阶段——检查资金、持仓、风控规则
  3. 转换阶段——把内部订单格式转成交易所协议
  4. 发送阶段——通过网卡直接发出去

每个阶段之间用无锁队列连接。为什么不用锁?因为锁会阻塞线程,阻塞就是延迟的元凶。我在项目中遇到过,一个简单的互斥锁没处理好,导致订单处理时间从5微秒飙到了50微秒。后来换成无锁的环形缓冲区,问题才解决。

// 伪代码:无锁环形缓冲区
struct LockFreeRingBuffer {
    atomic<uint32_t> head;
    atomic<uint32_t> tail;
    OrderSlot slots[MAX_SLOTS];
    
    bool push(Order& order) {
        uint32_t t = tail.load(memory_order_relaxed);
        uint32_t n = (t + 1) % MAX_SLOTS;
        if (n == head.load(memory_order_acquire))
            return false;  // 满了
        slots[t] = order;
        tail.store(n, memory_order_release);
        return true;
    }
};

小技巧:流水线的每个阶段最好绑定到独立的CPU核心上,避免上下文切换。我一般会留一个核心专门做订单发送,其他核心别碰它。

4.2 原子化操作设计

什么叫原子化操作?就是一件事要么做完,要么没做,没有中间状态。

订单处理里最怕什么?最怕半路崩溃。比如你扣了资金,但订单没发出去。或者订单发出去了,但状态没更新。这种问题在分布式系统里特别常见。

我建议把每个操作都设计成原子单元。举个例子:

// 原子化下单操作
struct AtomicOrderOperation {
    bool execute(Order& order) {
        // 第一步:预扣资金(可回滚)
        if (!reserveFunds(order)) {
            return false;
        }
        // 第二步:发送订单(不可回滚)
        if (!sendToExchange(order)) {
            // 回滚资金
            releaseFunds(order);
            return false;
        }
        // 第三步:记录状态(可重试)
        updateOrderStatus(order, OrderStatus::PENDING);
        return true;
    }
};

你看,每一步都有明确的失败处理逻辑。预扣资金失败了直接返回;发送失败了要回滚资金;状态更新失败了可以重试。这就是原子化的精髓——每个操作都有明确的边界和补偿机制。

注意:原子化不等于简单化。我曾经在一个项目里把原子操作粒度搞得太粗,一个操作里塞了太多逻辑,结果出了问题都不知道是哪一步失败的。后来拆成细粒度原子操作,每个操作只做一件事,排查问题快多了。

原子化操作还有一个好处:方便做幂等性。同一个订单重复执行,结果应该是一样的。交易所那边经常会有重复报单的情况,如果你的系统不幂等,那麻烦就大了。

4.3 订单状态机

订单状态机,说白了就是订单的一生。从出生到死亡,每个阶段都有明确的状态。

标准的状态流转是这样的:

New → Pending → Filled
                → Cancelled
                → Rejected

但实际项目中,状态远不止这些。我整理了一个更完整的版本:

状态 含义 可转换到
New 订单刚创建,还没处理 Pending, Rejected
Pending 已发往交易所,等待确认 Filled, Cancelled, Rejected
PartiallyFilled 部分成交 Filled, Cancelled
Filled 全部成交,终态
Cancelled 已撤销,终态
Rejected 被交易所拒绝,终态

状态机的实现,我推荐用状态模式或者查表法。查表法更直观:

// 状态转换表
struct StateTransition {
    OrderState from;
    OrderEvent event;
    OrderState to;
};

StateTransition transitions[] = {
    {New, SUBMIT, Pending},
    {New, REJECT, Rejected},
    {Pending, FILL, Filled},
    {Pending, PARTIAL_FILL, PartiallyFilled},
    {Pending, CANCEL, Cancelled},
    {PartiallyFilled, FILL, Filled},
    {PartiallyFilled, CANCEL, Cancelled},
};

关键点:状态转换必须是线程安全的。多个线程可能同时操作同一个订单的状态,不加锁的话状态就乱套了。我一般用原子变量加CAS操作来实现无锁状态转换。

嗯,这里要注意一个坑:交易所返回的状态和本地状态可能不一致。比如你发了撤单指令,但交易所那边已经成交了。这时候本地状态是Cancelled,但实际是Filled。怎么办?

我的做法是:以交易所的最终确认为准。本地状态只是参考,最终状态一定要等交易所的回包。我曾经因为这个吃过亏,本地显示撤单成功,结果第二天结算发现那笔单子成交了,亏了不少钱。

4.4 整体架构图

说了这么多,咱们画张图把整个执行引擎串起来:

执行引擎层架构图 策略层 无锁队列 接收阶段 校验阶段 转换阶段 发送阶段 交易所 订单状态机 New → Pending → Filled/Cancelled → Rejected 状态同步 每个阶段内部采用原子化操作设计,确保数据一致性和可回滚性

这张图把整个执行引擎的脉络理清楚了。策略层下来的订单,经过无锁队列进入流水线,每个阶段都是原子操作,状态机全程跟踪订单的生命周期。最后通过发送阶段到达交易所。

避坑指南:我曾经在流水线里加了一个日志打印,结果延迟直接翻倍。后来把所有日志都改成异步写入,才把延迟降回来。记住,执行引擎里任何同步IO都是敌人。

好了,执行引擎层的内容就这些。核心就三件事:流水线要快、操作要原子、状态要清晰。把这三点做好了,你的交易系统就成功了一大半。

专注资料整理