加密货币交易所撮合引擎究竟是如何工作的
技术 交易引擎 架构

加密货币交易所撮合引擎究竟是如何工作的

C
Codono Team
| · Updated March 10, 2026 | 3 min read

目录

决定交易所成败的那段软件

每位交易所创始人都热衷于谈论 UI:炫酷的 TradingView 图表、深色模式切换、移动端 App。这些对用户获取确实重要。但如果你想知道究竟是什么决定了你的交易所能否挺过第一个真正的交易日,请看撮合引擎。

撮合引擎是核心软件,它接收买入和卖出订单,判断哪些订单可以相互成交,并执行交易。听起来很简单,其实不然。设计糟糕的撮合引擎会在高负载下悄悄丢单、产生幽灵成交、算错余额,制造出让审计师都想辞职的账务噩梦。我调试过太多这样的系统,深知其中的痛苦。

让人意外的是,核心逻辑看起来少得具有欺骗性。把一笔买单与现有卖单撮合的基本算法,用不到 100 行代码就能写出来。但这个玩具级实现与生产级交易引擎之间,隔着大约 5 万行的边缘情况处理、持久化逻辑、恢复机制和性能优化。

下面我带你走一遍现代撮合引擎内部真正发生的事情,以及每个设计决策为何重要。

撮合引擎到底做什么

先忘掉教科书定义。以下是撮合引擎在每个交易日的每一毫秒里实际做的事:

  1. 接收传入订单——一笔买入或卖出请求,带有类型(限价、市价、止损)、价格(可能有)、数量和交易者 ID。
  2. 校验订单——检查价格是否满足最小价位(tick size)要求、数量是否满足最小手数、交易者余额是否充足,以及订单是否违反任何交易所规则。
  3. 尝试撮合——扫描订单簿对手方,寻找可成交的挂单。一笔 $50,000 的买单可以与任何 $50,000 或更低的卖单成交。
  4. 执行交易——每笔成交都会生成交易记录、更新双方交易者的余额,并调整订单簿。
  5. 处理剩余数量——如果传入订单未完全成交,要么挂入订单簿(限价单),要么被撤销(对手方没有剩余流动性的市价单)。
  6. 发布事件——向系统其余部分广播成交记录、订单簿更新和余额变动。

第 1 到第 6 步必须原子性地完成。不是”最终一致”,也不是”之后再对账”,而是原子性。如果第 4 步成功但第 6 步失败,就会出现交易者永远看不到的静默成交;如果第 3 步成功但第 4 步部分失败,订单簿上就会出现幽灵流动性。这两种情况摧毁信任的速度比任何黑客攻击都快。

这就是为什么对于任何给定的交易对,撮合引擎几乎总是单线程的顺序处理器。在这里,并发不是朋友,而是负担。谈到扩展时我们还会细说。

订单簿数据结构:性能的生死之地

订单簿是保存所有限价挂单的数据结构,按价格档位组织。它有两边:买盘(买入订单)和卖盘(卖出订单)。买盘从高到低排序,卖盘从低到高排序。最优买价和最优卖价——两边顶部的价格——定义了价差(spread)。

为订单簿选择正确的数据结构,是你最重要的架构决策之一。以下是现实中的选项:

有序数组或链表

最简单的方案:维护一个按价格排序的档位列表,每个档位包含一个订单队列。

优点: 实现极其简单。内存布局对缓存友好。需要遍历订单簿时迭代很快。

缺点: 插入是 O(n),n 为价格档位数。对于 BTC/USDT 这种有数千个活跃价格档位的交易对,这会很慢。删除由于需要移动元素也是 O(n)。

结论: 原型可以用。严肃交易量的生产环境不可接受。

平衡二叉搜索树(红黑树、AVL 树)

这是大多数生产级撮合引擎的选择。自平衡 BST 提供 O(log n) 的插入、删除和查找。通过维护指向最小和最大节点的指针,还能高效地找到最优买价和最优卖价。

优点: 所有关键操作都是 O(log n)。算法成熟易懂。易于维护最优买卖价指针。

缺点: 指针密集的结构容易造成缓存未命中。再平衡时的树旋转带来常数因子开销。

结论: 行业标准是有道理的。除非有非常特殊的需求把你推向别处,否则就应该用它。

哈希表加跳表

一些高频交易公司使用以价格档位为键的哈希表,配合跳表或类似结构来维护顺序。这样查找特定价格档位是 O(1),有序遍历是 O(log n)。

优点: 对于最常见的场景——把订单加入已存在的价格档位——快得惊人。

缺点: 正确实现更复杂。跳表部分对新价格档位的插入仍是 O(log n)。

结论: 如果你的性能分析显示大多数订单落在已存在的价格档位上(对交易活跃的交易对来说确实如此),就值得考虑。

现实世界中的混合方案

大多数严肃的实现实际上长这样:用平衡 BST 做价格档位索引,每个价格档位包含一个订单的双向链表(FIFO 队列)。同时维护一个从订单 ID 到订单节点的哈希表,这样撤单就是 O(1) 查找加 O(1) 链表移除。

这个用于撤单的哈希表至关重要。在任何活跃的交易所,撤单量通常是成交量的 10 到 50 倍。做市商在不停地下单和撤单。如果你的撤单路径慢,整个引擎都会被拖垮。

价格时间优先:公平的契约

价格时间优先,也叫 FIFO 撮合,是几乎所有正规交易所使用的标准算法。规则很简单:

  1. 订单首先按价格排序。$50,001 的买单先于 $50,000 的买单成交。
  2. 同一价格档位内,订单按时间排序。先到达的订单先成交。

这听起来理所当然,但含义深远。价格时间优先本质上是一份公平性承诺:如果你在某个价位先挂了单,没有人能通过之后以相同价格下单来插队。你在队列中的位置就是你的财产。

为什么这如此重要?因为没有严格的价格时间优先,你就为一整类操纵行为打开了大门。交易所运营者理论上可以重排队列以偏袒某些做市商;连接更快的交易者可以通过撤单再下单来插队。这些不是假设场景——它们都曾在不光彩的交易所真实发生过,这也正是监管机构关心撮合引擎公平性的原因。

一些交易所尝试过替代性的优先级方案——按比例撮合(pro-rata,部分期货市场使用),即同一价格档位的成交量按挂单比例分配;或者数量时间优先,即大额订单优先。但对于标准的现货交易所,价格时间 FIFO 是标准,偏离它需要非常充分的理由,并向交易者明确披露。

让人栽跟头的实现细节是:你的时间戳精度很重要。如果时钟精度只有毫秒级,同一毫秒内到达的两笔订单排序就是任意的。生产级引擎使用微秒或纳秒级时间戳。而且时间戳必须在订单进入撮合引擎处理队列的那一刻打上去,而不是在它到达 API 网关时。

延迟与吞吐:“快”到底意味着什么

人们谈论撮合引擎性能数字时往往没有上下文,所以我列一些真实基准。

一线交易所(Binance、Coinbase、Kraken): 这些引擎处理单笔订单的时间是几微秒。它们公布的数字通常声称每个交易对每秒 10 万笔以上订单,撮合延迟低于 5 微秒。在这个级别,你用 C++ 或 Rust 写代码,使用无锁数据结构,把线程绑到 CPU 核心上,并绕过内核网络协议栈。

二线交易所(中型、成熟的平台): 订单处理在 50 到 500 微秒区间。吞吐量每秒 1 万到 5 万笔订单。通常用 Java、Go 或 C++ 编写。绝大多数正规交易所运行在这个水平,说实话,对大多数用例来说绰绰有余。

三线交易所(初创公司、较小的平台): 处理时间在 1 到 10 毫秒区间。吞吐量每秒 1,000 到 1 万笔订单。常用 Python、PHP、Node.js 或 Java 编写。对日交易量低于 1,000 万美元的交易所完全够用。

有句话没人愿意大声说出来:对 95% 的新交易所来说,三线性能完全没问题。如果你的交易所每天成交 1,000 笔——这已经是非常健康的上线水平——你需要的平均吞吐量大约是每秒 0.01 笔订单。即使按峰值是平均值 100 倍来算,你大概也只需要每秒 1 笔订单。一个用 Python 写的、跑在单核上的撮合引擎就能轻松应对。

性能问题不在于上线当天,而在于你成长之后。你的引擎能否从每秒 1,000 笔扩展到每秒 10 万笔而不用彻底重写?这才是真正重要的架构问题。

这也是为什么选择经过验证的交易所软件很重要。Codono 加密货币交易所平台自带的撮合引擎已经在生产规模下做过压力测试,你不必赌自己写的实现能挺过第一次流量高峰。而且因为交易所脚本完全不加密,你的团队可以直接审查和调优撮合逻辑。

订单类型:比看上去复杂得多

限价单和市价单很直接。但一旦加入止损单,事情就变得有趣了;再加入 OCO(二选一)订单,你的状态管理复杂度大约翻一倍。

限价单

主力订单类型。“以 $49,500 或更优价格买入 1 BTC”。订单挂在订单簿上,直到成交、被撤销或过期。实现很直接——校验、尝试与对手方撮合、把未成交数量挂入订单簿。

市价单

“以当前市价买入 1 BTC”。订单沿对手方订单簿逐档吃单,直到完全成交或没有流动性为止。关键的实现细节:你需要价格保护机制。在稀薄的订单簿里,一笔市价买单不应该因为有人恶作剧挂了个卖单就以 $999,999 成交。大多数引擎实现最大滑点参数,或者基于相对最新成交价的百分比偏差设置隐式限价。

止损单与止损限价单

这是撮合引擎真正棘手的地方。止损单是条件订单:“当市场价触及 $48,000 时,提交一笔市价卖单。“止损单本身不挂在可见的订单簿上,它待在单独的数据结构——止损簿(stop book)——里,当有交易以触及或穿越止损价的价格成交时被触发。

棘手的是级联止损。如果 $48,000 的一笔成交触发了止损卖单,而这笔止损卖单的执行把价格压到 $47,500,又触发更多止损,进一步压低价格……可能在几毫秒内形成级联,把订单簿抽干。历史上几次闪电崩盘就是这么发生的。你的引擎需要熔断机制——单位时间窗口内的最大价格波动限制、订单簿最小深度检查,以及在情况失控时暂停撮合的能力。

OCO(二选一)订单

OCO 把两笔订单关联起来:通常是一笔止盈限价单和一笔止损单。任意一笔成交,另一笔自动撤销。这需要维护订单之间的关系图,而且撤单必须与成交原子性地发生。如果止损单触发并成交,但止盈单的撤销延迟了哪怕几毫秒,就可能两笔订单都成交——让交易者持有一个他们不想要的仓位。

Codono 的引擎原生处理这些高级订单类型,包括合约交易的订单类型,如只减仓(reduce-only)和只做 Maker(post-only)订单,它们又增加了一层复杂度。

会摧毁交易所的边缘情况

这一节能把真正上线过撮合引擎的工程师和只读过相关文章的人区分开来。以下是造成过真实生产事故的边缘情况:

自成交防护(STP)

当交易者有一笔 $50,000 的挂单卖出,又提交了一笔 $50,000 的买单时会发生什么?没有 STP,交易者会和自己成交——两边都付手续费,却没有任何经济意义。做市商的算法同时运行多个策略时,经常会意外发生这种情况。

你需要一个 STP 策略。三个常见选项:

  • 撤销最新单: 会导致自成交的传入订单被撤销。
  • 撤销最老单: 挂单被撤销,传入订单继续撮合。
  • 两单都撤销: 两笔订单都被撤销。

大多数交易所默认撤销最新单,但你应该让它可以按交易者配置。做市商对此有强烈的偏好。

粉尘订单

一笔 0.00000001 BTC 的订单怎么办?一笔成交后剩余 0.000000003 BTC 怎么办?这些粉尘量太小,既不能交易也不能提现,却会堵塞你的订单簿和数据库。

你需要最小订单数量、最小名义价值(价格乘以数量必须超过某个阈值,通常是几美元),以及一个定期清扫低于最小额度余额的粉尘收集机制。

最小价位与最小手数强制

价格必须是最小价位的整数倍(比如 BTC/USDT 为 $0.01)。数量必须是最小手数的整数倍(比如 0.00001 BTC)。如果你不在撮合引擎层面强制这些规则,就会收到 $50,000.003 这种与其他任何订单都对不齐的价格,永远挂在订单簿上。更糟的是,你会遇到导致余额对不上的小数精度问题。

溢出与精度

这一条坑过的交易所比任何其他问题都多。如果你在用浮点数运算处理价格和数量,立刻停下来,现在就停。浮点运算产生的舍入误差,经过数百万笔交易的累积,会变成真实的余额差异。生产级撮合引擎必须使用定点整数运算(把 $50,000.00 表示为整数 5000000)或任意精度的小数库。没有第三种选择。

撮合后余额检查

即使有交易前余额校验,你也需要交易后余额断言。每笔成交执行后,验证所有用户余额加交易所手续费之和等于预期总额。如果这个不变量被破坏,立即停止撮合并排查。这是你抵御那些会缓慢耗尽用户资金的 bug 的最后一道防线。

扩展策略:单线程的困境

还记得我说过撮合引擎对每个交易对几乎总是单线程的吗?这带来一个显而易见的扩展挑战。现代硬件上的单个线程大概每秒能处理 10 万到 50 万次简单操作。对大多数交易所这够用了。但如果不够呢?

垂直扩展

堆更快的硬件:更高主频的 CPU、更大的 L3 缓存、更快的内存。这是首先该尝试的,而且往往足够。现代高频交易引擎跑在定制硬件上,用基于 FPGA 的网卡绕过 CPU 直接完成基本撮合操作。但对典型交易所来说,一台配备 5+ GHz 处理器和 128GB 内存的高配服务器就能扛住巨大的交易量。

按交易对水平扩展

最简单的水平扩展策略是每个交易对跑一个撮合引擎实例。BTC/USDT 一个引擎,ETH/USDT 一个引擎。它们完全独立——没有共享状态,不需要协调。扩展能力随交易对数量线性增长。

难点在于余额管理。如果一个交易者有 10 个 BTC,在 5 个 BTC 交易对上挂单,每个撮合引擎都需要知道可用余额。标准做法是设一个中心化余额服务,每个引擎在接受订单前向它查询。这个余额服务会成为新的瓶颈,但它扩展起来简单得多,因为它只做简单的读写操作,不做复杂的撮合逻辑。

交易对内的分片

对于交易量最高的交易对,你可能需要对订单簿本身分片。一种做法是按价格区间分片——一个引擎处理 $49,000 到 $50,000 的价格,另一个处理 $50,000 到 $51,000。但跨分片撮合(跨越价格区间的市价单)复杂得可怕。

说实话?除非你在处理 Binance 级别的交易量,否则你不需要这个。而如果你真的在处理 Binance 级别的交易量,你还有更大的架构问题要先解决。

对大多数交易所运营者来说,明智的做法是用流动性聚合来补充订单簿深度,而不是试图对单个交易对的撮合引擎分片。

内存与磁盘:持久化的问题

撮合引擎的订单簿存在内存里,必须如此——磁盘 I/O 增加的毫秒级延迟对实时撮合完全不可接受。但引擎崩溃时怎么办?你不能就这么丢掉所有挂单。

标准架构使用三层:

第一层:内存订单簿。 这是热路径。所有撮合都在这里发生。正常运行期间零磁盘 I/O。

第二层:预写日志(WAL)。 每个传入订单和每个撮合事件,在引擎处理之前先追加写入磁盘上的顺序日志。如果引擎崩溃,从最近的检查点重放日志即可重建订单簿。顺序写入很快——一块 NVMe 硬盘每秒可承受 50 万次以上顺序写操作,绰绰有余。

第三层:定期快照。 每 N 秒(或每 N 个事件),引擎把订单簿的完整快照写入磁盘。这限制了崩溃后需要重放 WAL 的回溯距离。没有快照的话,运行 24 小时后崩溃可能要重放数百万个事件。

WAL 是关键部分。它让你在不牺牲延迟的前提下获得持久性。代价是磁盘写入是异步的——交易在内存中撮合成功和持久化到磁盘之间有一个极小的窗口(微秒级)。如果机器恰好在这个窗口内断电,那些事件就丢了。对大多数交易所,这个风险可以接受。对于需要绝对零丢失保证的场景,在确认成交之前先向备用机器做同步复制。

Codono 如何处理这一切

从零构建撮合引擎意味着解决上面描述的每一个问题:数据结构、优先级逻辑、边缘情况、持久化、扩展——全部。然后永远维护下去,因为市场在演变,新订单类型会不断出现。

这正是我们如此构建 Codono 交易引擎的原因。Codono 交易所软件包含一个生产级撮合引擎,具备:

  • 价格时间优先 FIFO 撮合,微秒级时间戳精度
  • 全部标准订单类型,包括限价、市价、止损、止损限价和 OCO
  • 自成交防护,可按 API key 配置策略
  • 定点数运算贯穿整个交易流水线——余额计算附近没有任何浮点数
  • 预写日志,自动检查点与恢复
  • 水平扩展,每个交易对独立的引擎实例
  • 内置熔断机制,防范闪电崩盘

引擎还与我们的流动性引擎直接集成,所以即使你的自有订单簿较薄,传入订单也能与聚合的外部流动性撮合。这对新交易所尤其重要,因为培育自有流动性需要时间。

对于需要程序化接入的运营者,API 层提供低延迟的 WebSocket 订单簿更新和成交回报推送,做市商可以高效地与引擎交互。

总结

撮合引擎概念上简单得具有欺骗性,实现上却难得残酷。核心算法你脑子就装得下。但生产级的问题——持久化、恢复、公平性、精度、边缘情况、负载下的性能——代表着数年的工程投入。

我的诚实建议:除非撮合引擎研发是你的核心能力和竞争优势,否则不要从零构建。使用经过实战检验的软件,把你的工程资源集中在真正能让交易所脱颖而出的地方——用户体验、上币、合规、市场推广。撮合引擎应该是一个你部署就好的已解决问题,而不是一个烧光你跑道的科学项目。

成功的交易所不是那些撮合引擎最花哨的,而是那些做出明智的自建与购买决策、快速上线、并在对用户重要的事情上持续迭代的。你的撮合引擎需要正确、足够快、可靠。它不需要是一件艺术品。它需要能用。


Codono 的撮合引擎以价格时间优先原则每秒处理数千笔订单,内置熔断机制和流动性聚合。查看现货交易所功能页或预约演示亲身体验。

技术 交易引擎 架构 基础设施
C

Codono Team

Codono builds enterprise-grade crypto exchange software deployed by 250+ operators across 40+ countries. Our team writes from production experience running spot, derivatives, custody, and compliance at scale.

View all posts by Codono Team →

Build Your Exchange with Codono

Complete crypto exchange software with spot, futures, P2P, and 15+ blockchains.