如何利用确定性重放构建可靠的交易系统

原标题How to Build a Reliable Exchange with Deterministic Replay

撮合引擎、组播和状态机复制可以让高吞吐量交易系统具备恢复能力。本讲座解释了这一设计及其权衡。

频道
Jane Street
发布
2017年3月13日
视频
38:17
阅读
9 分钟
幻灯片
6
原文
英语
在 YouTube 打开
往下滚动阅读时,视频会缩到右下角继续播放。字幕跟随阅读语言。
阅读语言共 2 种语言可读
由 AI 从英语原文翻译。打开“对照原文”可以逐段核对。
摘要

30 秒看懂

一位 Jane Street 讲者以其 JX 撮合引擎的架构为例,解释交易系统的需求。讲座从订单簿和公平性出发,逐步介绍单一撮合引擎、组播、重传、确定性重放和被动副本,并讨论性能、并行性与交易冲突。

  1. 1公平性体现在实现细节中连接顺序可能无意中让部分参与者更快获得信息,从而激励他们争抢。
  2. 2简单的中央引擎一个撮合引擎维护订单簿,独立进程则处理客户端和下游消费者。
  3. 3通过重放事件恢复应用从有序消息流中确定性地重建状态;被动撮合引擎跟随主引擎的输出。
  4. 4复制存在共模风险相同代码可能因同一输入而失败,因此细致测试至关重要。
  5. 5排序解决竞争性变更贡献者提出主题的下一个序号,在应用竞争事件后重试。
  6. 6速度让系统更简单快速处理和重放使恢复与可重复测试成为现实。

要点建议

  1. 交易系统除了撮合订单,还必须处理吞吐量、公平性、持久性和客户端行为。
  2. 组播将有序消息流分发给应用,重传机制则修复丢包。
  3. 确定性状态机重放让故障进程能够重建状态,但相同副本可能共享软件缺陷。
  4. 低延迟支持吞吐量和快速恢复,使架构可以更加简单。
  5. 按交易品种分区可以提高并行度,但会使跨品种原子风险控制和输出排序更复杂。
  6. 按主题排序可以解决相互竞争的状态变更,同时让客户端可见事件能够恢复。
全文

按章节阅读整理后的全文

只看原始文稿
01

订单簿与交易系统需求

讲者解释买卖报价、撮合与撤单。规模、公平性和持久性共同塑造系统设计。

理解交易系统可以从数据结构和消息入手。限价订单簿按价格递减排列买单,按价格递增排列卖单:卖方希望获得最高买价,买方则希望获得最低卖价。最佳买价和卖价相接的地方称为 inside 或 touch。

限价订单簿
幻灯片 1 / 6限价订单簿买价和卖价按价格排列在订单簿两侧。

参与者可以提交订单,例如以每股 33.29 美元买入 500 股 Apple 股票。订单簿通常按价格和时间排序。如果新卖单与已有买单的价格相交,它就是可成交订单:交易系统确定性地应用规则、生成交易,并向参与者发送成交报告。未成交订单也可以撤销。

添加买单
幻灯片 2 / 6添加买单新订单按照价格和时间优先级进入订单簿。

讲者估计,新增订单约占市场消息的一半,撤单占 30%–40%,成交占 1%–2%,其余包括撤单替换和其他消息。并非所有系统都能简单地按交易品种分区:某些订单类型需要以原子方式同时影响两个品种。

以美国股票市场为例,规模大约是峰值每秒 300 万条消息、数千条连接、数百万个有效订单和约 10,000 个交易代码。公平意味着尽量让信息同时到达参与者,也包括避免系统设计无意中带来优势。

在 Island,一台市场数据服务器曾按顺序遍历 TCP 连接发送消息。参与者发现排在前面的连接会更早收到信息,于是在系统关闭后到次日开市前,每秒都有数千次连接尝试争夺首位。小小的实现细节也会制造竞速的动机。

可靠性和持久性至关重要。一旦通知双方交易已成交,他们可能投入资金并进行其他交易;如果系统遗忘或撤回成交,就会造成严重问题。系统还需要隔离行为不当的客户端,无论这种行为源于疏忽还是订单类型产生的激励。

价格改善就是激励的一个例子。传统上,当买卖价格相交时,主动订单获得价格改善的好处,从而鼓励参与者提交最积极的报价。讲者回忆,有个系统反而让隐藏订单获得这一好处,结果客户端逐个试探价格,直到成交。

02

撮合引擎及其消费者

单一引擎维护订单簿,独立进程负责客户端、报告、市场数据和辅助工作。架构力求及时向每个消费者分发信息。

提出的架构看起来有些不寻常:一个撮合引擎应用在内存中维护订单簿,独立的客户端端口进程接受连接并发送订单和撤单。讲者回忆,Island 早期曾用一台高性能 x86 机器运行整个市场,这让习惯大型机的技术人员感到惊讶。

撮合引擎与客户端端口
幻灯片 3 / 6撮合引擎与客户端端口单一内存引擎连接独立的客户端端口进程。

其他应用也需要及时获得信息。Drop port 向清算公司发送选定的活动,交易报告器报告成交,市场数据服务则发布匿名化、经过过滤的交易流。每个消费者都可以根据同一底层活动构建自己的视图。

交易系统的消费者
幻灯片 4 / 6交易系统的消费者交易报告、清算、客户端端口和市场数据都消费引擎产生的活动。

撮合引擎通过将工作移到外部来保持简单。撤单助手观察要求未来撤销的订单,并在指定时间发送撤单;多个实例可以同时竞争,因此一个失败也不会让功能停止。拍卖计算同样可以单独运行,再把结果发送给引擎,而不增加连续撮合的负担。

03

组播与恢复

组播分发消息,重传服务器修复缺口。确定性重放与被动引擎支持恢复,但也存在共享代码导致的故障风险。

组播让这一架构成为可能:发往逻辑地址的数据包由网络设备近乎同时复制给感兴趣的接收方。因此成交双方、市场数据服务、drop port 和交易报告器可以同时看到事件。但 UDP 可能丢包,本身也不提供排序或重传。

重传服务器记录消息,并利用序号检测缺口。它们互相请求缺失消息,必要时也可从撮合引擎的内存副本中获取。这增加了可靠性,同时让引擎免于承担大部分恢复工作。

客户端端口通常是独立进程,使用 FIX 或其他协议向客户端提供有序 TCP 连接。内部交易则使用 UDP。端口连接私有组播网络和公共访问网络,验证并规范化客户端流量、隔离异常客户端,并协助流量控制。

引擎预先分配订单表示,并在确认订单时向端口返回定位符。端口检查传入的撤单,找到相应订单并提供该定位符,让引擎能够快速定位。这个定位符供内部使用,并不是发给客户端的内存地址。

重传消息流使确定性状态机复制成为可能。每个应用通过重放当天开始以来的有序事件重建状态。如果客户端端口进程故障,另一个进程可以重放消息流、恢复未完成订单和消息序号,并与重新连接的客户端核对遗漏的消息。

撮合引擎自身也需要备用实例。被动引擎监听主引擎的有序输出,而不是独立监听各端口,因为 UDP 到达顺序可能不同。它运行相同的确定性状态机和代码,识别客户端发起的消息,并独立生成相应的成交结果。

如果主引擎发出输入后、完成所有相应成交消息之前发生故障,被动引擎可以从该输入生成相同输出。操作人员会按照谨慎的故障切换流程处理。讲者将其与 Paxos 等共识方法比较,后者增加复杂性和一次往返;他也说明这反映的是自己对交易系统实践的经验。

一位观众问及关联故障:如果同一输入使主引擎崩溃,是否也可能使运行相同代码的备用引擎崩溃?讲者承认这是实际的弱点。硬件或操作系统故障可能不同,但状态机必须格外严谨并经过充分测试;共享代码是一把双刃剑。

04

速度与并行性

低延迟支持吞吐量和快速重放。按交易代码并行处理会在跨品种风险控制和有序输出方面产生权衡。

速度的意义不止于漂亮的延迟指标。每个应用都会看到所有消息,必须快速丢弃不相关内容。每个客户端端口只允许一个尚未确认的交易在途,因此单端口吞吐量取决于往返延迟。较小的 TCP 套接字缓冲区最终会通过 TCP 流量控制向客户端施加反压。

快速重放也简化了恢复。如果系统能在 30 秒或一分钟内重建状态,就可以重放当天消息,而不必为许多不同应用维护单独的状态传输机制。讲者说,使用大体通用的硬件和优质网卡就可以做到这一点。

讲者曾用 C++、Java 和 OCaml 实现系统,他认为 OCaml 相对简单的运行时有助于实现可预测的性能。关键路径避免过度分配:数据理想情况下无需复制即可到达,代码读取值、用复用的池化对象更新辅助结构,然后重复这一过程,所需内存提前准备好。

系统可以并行化,但按交易代码分区会牺牲一些有用的原子操作。Jane Street 的 ETF 做市涉及彼此重叠的成分,因此单一引擎可以原子地执行全市场风险限额。将撮合分给独立引擎会使这些保证更加复杂。

在单台机器内,多个核心可以重叠执行内存读取。讲者曾对撮合引擎做性能分析,发现一次查找占约 30% 的执行时间,因为通过订单定位符访问数据时常常发生缓存未命中。批量预取可以重叠这些访问,尽管性能可能不如多个核心。

对引擎分区也会使输出顺序复杂化。如果多个撮合引擎向同一个客户端流发送消息,决定响应顺序会产生必须能够恢复的状态。有些交易所改为提供多个序号空间,不承诺它们之间的相对顺序,实际上相当于代理多个独立连接。

05

交易冲突与排序

每个主题的序号协调相互竞争的变更。撤单与成交冲突的示例展示了重试和客户端可见结果的恢复。

接着讲者将锁解释为处理竞争性变更的通用机制。每个主题都有自己的序号,与全局消息序号分开。贡献者为某个主题提出下一个交易;如果另一个交易抢先获得该序号,贡献者就应用这个事件、更新状态并重试。只允许一个请求在途使回滚更简单。

在示例中,端口 P1 以主题序号 3 提出以每股 43.50 美元买入 100 股。引擎接受并组播该请求。随后 P2 以自己的序号 8 提出以每股 43.49 美元卖出 100 股;价格相交,因此引擎在两个主题上生成成交消息。

带序号的买单提议
幻灯片 5 / 6带序号的买单提议时序图展示贡献者提出订单以及撮合引擎确认订单。

与此同时,P1 尚不知道成交已经发生,于是用序号 4 提出撤单。引擎已经将该序号用于成交,因此悄悄丢弃过时的提议。P1 看到序号 4 来自另一发布者,便应用成交并重试原本的撤单;订单已经不存在,于是它在序号 5 提出撤单拒绝。

成交与竞争中的撤单
幻灯片 6 / 6成交与竞争中的撤单图中展示撤单与同一主题上的成交消息发生竞争。

这种竞争理论上可能导致活锁,但讲者说,这类交易通常很快结束,因为已经成交的订单不再存在,无法继续撤销。在其他场景应用这种架构需要判断。一位观众问为什么撤单拒绝必须返回引擎:在这个协议中它有序号,而客户端可见状态必须来自全局有序消息流才能恢复。

另一个问题涉及一次成交为两个主题产生消息时的原子性。讲者说,有序输入是关键事件:只要它被记录,被动状态机就能确定性地重新生成后续消息。系统仍要求每条消息在数据包和序号层面具有原子性。

06

设计经验

讲者将性能与简单性、测试和可解释性联系起来。架构中的各项选择共同支撑稳健的交易系统。

讲者总结说,速度和确定性带来的不仅是更快响应,也让简单稳健的架构成为可能。状态机复制使测试和恢复可以重复:新引擎可以重放数周的历史数据,也可以进行模糊测试。需要遵守的原则是,每次状态变更,包括定时器驱动的动作,都必须表示在有序消息流中。

确定性也有助于向监管机构解释决策:系统可以重建每个时点掌握的信息,而不必猜测哪个并发事件先到。系统还可以进一步并行化,讲者也讨论了其他方法,但他强调,这套设计的各项选择相互配合,以清晰的方式解决交易系统问题,并自然扩展。

同频道的其他文章

1:48:29EN → 简体中文
JSJane Street· 2026年10月16日AI 如何改变编程:John Krazit 与 Ron Minsky 的对话Ron Minsky, John Krazit · Jane StreetJane Street 的 AI 工具负责人和 Signals and Threads 主持人探讨智能体基础设施、安全、代码审查,以及软件开发体验的变化。编程智能体Developer productivity评估+2
1:36:37EN → 简体中文
JSJane Street· 2026年9月23日Aaron Bauer:在 AI 时代教授软件工程Ron Minsky, Aaron Bauer · Jane Street当模型能够生成代码时,学习目标、主动练习和编辑器设计如何影响工程教育。AI in EducationSoftware engineeringEducation+2
9 张幻灯片1:05:45EN → 简体中文
JSJane Street· 2026年9月11日Cyrus Omar:实时编程与人与 AI 共享的计算公共空间Cyrus Omar通过 Hazel 演示,探讨即时反馈、结构化协作和精细权限如何帮助人类与 AI 智能体共同工作。Programming LanguagesHuman-AI Collaboration开发流程与测试+2
1:25:00EN → 简体中文
JSJane Street· 2026年9月2日Eric Mannes:如何将杂乱的外部数据用于交易研究Ron, Eric Mannes · Jane Street从商品期货和 Excel 风险系统,到时点数据集和 AI,Eric Mannes 解释 Jane Street 数据工程背后的领域知识。Data quality & curation开发流程与测试Investing+2
7 张幻灯片1:16:29EN → 简体中文
JSJane Street· 2026年8月26日Grant Sanderson 与 Alok Puranik:从群论看位置编码Grant Sanderson, Alok Puranik · 3Blue1Brown从注意力机制的假设出发,他们推导矩阵指数,并将旋转、衰减和 Jordan 块与 RoPE、ALiBi 联系起来。Transformers & attentionMathematics机器学习基础+1
27:39EN → 简体中文
JSJane Street· 2026年8月21日3Blue1Brown 与 Jane Street 谈交易、机器学习和文化Grant Sanderson, Nitya, In Young, Craig, Alok · 3Blue1BrownGrant Sanderson 与 Jane Street 的同事讨论做市、共享算力、研究,以及他们加入公司的不同经历。Machine LearningFintechCompany Culture+2