一、撮合引擎概述

大家好,我是你们的老朋友。今天咱们聊聊撮合引擎——这个在交易系统里最核心、也最神秘的组件。

说实话,我入行那会儿,第一次听到「撮合引擎」这四个字,脑子里浮现的是菜市场里的大妈在撮合买卖双方。后来真正上手做低延迟系统,才发现这玩意儿比菜市场复杂一万倍。

1.1 什么是撮合引擎

撮合引擎,说白了就是一套规则引擎。它的任务很简单:把买单和卖单匹配起来,然后成交

你想想看,交易所里每秒有成千上万笔订单飞进来。有人想买,有人想卖。谁跟谁成交?什么价格成交?成交多少?这些事,都得靠撮合引擎来搞定。

我个人习惯把撮合引擎比作一个「自动化的红娘」。它不生产订单,它只是订单的搬运工——把合适的买方和卖方拉到一起,然后让他们「牵手成功」。

核心定义:撮合引擎是交易系统中负责执行价格优先、时间优先原则,将买卖订单进行匹配并生成成交记录的软件模块。

我在项目中遇到过不少团队,把撮合引擎和订单管理系统混为一谈。其实它们分工很明确:订单管理负责「接单」,撮合引擎负责「配对」。一个是前台接待,一个是后台红娘。

1.2 纳斯达克撮合引擎在交易系统中的位置

咱们来看看纳斯达克的整体架构。我画了一张图,帮你理清位置关系。

纳斯达克交易系统架构简图 客户端层 券商交易终端 | 算法交易系统 | 高频交易客户端 接入层(Gateway) 协议转换 | 会话管理 | 速率限制 | 身份认证 核心交易层 订单管理(OMS) 撮合引擎(Matching Engine) 风控模块 数据层 行情发布 | 成交记录 | 清算数据 | 审计日志 ★ 核心中的核心

看到了吗?撮合引擎就在核心交易层的正中间。它左边连着订单管理,右边挨着风控模块,下面接着数据层。这个位置决定了它的重要性——所有订单最终都要经过它才能成交

我记得有一次给客户做系统优化,他们非要把风控逻辑塞进撮合引擎里。我说不行,这会拖慢撮合速度。后来他们自己试了,延迟从5微秒飙到了50微秒,乖乖改回去了。

避坑指南:我曾经见过一个团队,把撮合引擎和行情发布放在同一个进程里。结果行情数据量一大,撮合延迟就跟着抖。记住:撮合引擎要独立部署,别跟其他模块抢CPU。

1.3 核心功能

撮合引擎到底干哪些活?我列个清单给你看。

  1. 订单接收与校验——检查订单格式对不对、价格是否合理、数量是否合规
  2. 订单簿管理——维护买单队列和卖单队列,按价格和时间排序
  3. 撮合匹配——按照价格优先、时间优先的原则,找到匹配的买卖单
  4. 成交生成——计算成交价格、数量,生成成交记录
  5. 订单簿更新——成交后更新订单簿,移除已成交或部分成交的订单
  6. 行情推送——将最新的买卖盘口数据推送给行情系统

你可能会问:就这六件事?听起来也不难啊。

嗯,这里要注意。功能看起来简单,但难在「快」和「准」两个字。我举个例子你就明白了。

一个真实的场景:假设现在买单队列里有1000个买单,卖单队列里有800个卖单。突然来了一笔市价买单,要买10万股。撮合引擎需要在几微秒内,从800个卖单里找到最便宜的、时间最早的卖单,一笔一笔地匹配,直到买够10万股。同时还要更新订单簿、生成成交记录、推送行情。

这还没完。如果同时有1000笔订单涌进来呢?如果网络延迟导致订单顺序乱了呢?如果价格相同但时间戳冲突了呢?

所以说,撮合引擎的核心功能虽然只有六条,但每一条背后都是满满的工程细节。

1.4 性能指标

做量化交易的人,最关心的就是撮合引擎的性能。我一般看四个指标:

指标名称 定义 纳斯达克水平 行业要求
延迟 从收到订单到返回成交确认的时间 平均 < 10 微秒 < 100 微秒
吞吐量 每秒能处理的订单数量 峰值 > 100 万笔/秒 > 10 万笔/秒
抖动 延迟的波动范围(P99 - P50) < 5 微秒 < 20 微秒
可用性 系统正常运行时间占比 99.999% 99.99%

这四个指标里,我最看重的是「抖动」。为什么?

你想想看,如果延迟是10微秒,但有时候突然跳到100微秒。做高频交易的人最怕这个——他们没法预测你的系统什么时候会「卡一下」。我见过一个做市商团队,因为撮合引擎抖动太大,被迫把订单撤走,换到别的交易所去了。

注意:不要只盯着平均延迟看。P99延迟和抖动才是真正决定用户体验的关键。我曾经优化过一个系统,平均延迟从20微秒降到了8微秒,但P99延迟还是50微秒。结果客户说:「你的系统还是不稳。」后来我花了两个月,才把抖动压到5微秒以内。

说到性能优化,我给大家看一段伪代码。这是撮合引擎里最核心的订单簿查找逻辑:

// 订单簿查找 - 核心路径
// 使用跳表(Skip List)实现价格排序
// 时间复杂度:O(log n)

Order* find_best_match(OrderBook* book, Order* incoming) {
    // 1. 根据买卖方向选择队列
    PriceQueue* target_queue = (incoming->side == BUY) 
        ? book->sell_queue   // 买单找卖单
        : book->buy_queue;    // 卖单找买单
    
    // 2. 找到最优价格(最高买价/最低卖价)
    PriceNode* best = target_queue->head;
    if (!best || !is_price_acceptable(incoming, best)) {
        return NULL;  // 没有可匹配的订单
    }
    
    // 3. 按时间顺序取出最早订单
    Order* matched = best->orders->front();
    
    // 4. 检查数量是否匹配
    uint64_t trade_qty = min(incoming->qty, matched->qty);
    
    // 5. 更新订单状态
    update_order(incoming, trade_qty);
    update_order(matched, trade_qty);
    
    return matched;
}

这段代码看起来简单,但实际生产环境里,每一行都有讲究。比如第2步的「is_price_acceptable」,在纳斯达克用的是整数比较,连浮点数都不用——因为浮点数比较会引入额外的CPU指令。

我个人习惯在撮合引擎里用跳表(Skip List)而不是红黑树。为什么?因为跳表的并发性能更好,而且实现起来不容易出错。红黑树虽然理论复杂度一样,但实际工程中,跳表的缓存友好度更高。

一个小技巧:我曾经在优化撮合引擎时,发现订单簿的查找占了40%的CPU时间。后来我把价格队列从链表改成了数组+指针池,延迟直接降了一半。记住:内存布局比算法更重要。

好了,这一章的内容就到这里。撮合引擎看似简单,但真正做好,需要你对计算机体系结构、数据结构、并发编程都有很深的理解。后面我们会一步步深入,把每个细节都掰开揉碎了讲清楚。

记住一句话:撮合引擎不是写出来的,是调出来的


公众号:蓝海资料掘金营,微信deep3321