这页是随时翻的架构总图,和硬件地图是一对:那页讲一次访存经过哪些硬件,这页讲一条行情经过哪些线程。三部分按“在哪 → 多快 → 怎么切”排:
后面讲到新的组件,都补在这一页。
1. 架构地图:一条行情经过哪些框
绿框是热线程,各绑一个隔离核;蓝框是线程之间交接数据的结构;黄框是汇合点;最下面一排虚线框是旁路。黑线是行情到报单,红线是回报回来,灰色虚线是旁路。点图打开可交互的版本:点一个框看它做什么、为什么这样设计;选一条路径,一步一步看怎么走。
1.1 一条行情的路:行情 → 报单
- 交易所把行情按通道组播出来,网卡收到包。
- 行情接入线程解码,按通道查序号,断档了就去补。
- 它先把这条写进广播环,给落盘、监控这些不赶时间的读者,写完就走(#5)。
- 再按股票查表,写进负责这只股票的分片队列(#4)。同一只股票永远进同一片,顺序不会乱。
- 分片线程更新这只股票的盘口,再增量更新它的因子,只碰自己那些股票的状态。
- 按 bar 截面的策略,要等所有分片算完这根 bar,再一起排名、打分。
- 决策线程里,策略定买什么、卖什么、撤哪些,过事前风控,交给订单管理;大单拆成小单按时间发。
- 下单网关编码成协议发出去,经券商柜台到交易所。
1.2 一笔回报的路:成交 → 持仓 → 策略
- 交易所撮合以后发出回报:确认、成交、撤单都算。回报经柜台回到下单网关。
- 订单管理更新这笔单子的状态:部分成交、全部成交、已撤。
- 账户状态跟着变:持仓、资金、在途单。它只让决策线程一个人改。
- 下一次决策用的就是新的持仓和资金,回路闭合;推送把变化送到交易员的屏幕上。
1.3 两种节奏:按 bar 截面,还是逐笔触发
按 bar 截面(多因子选股):所有股票算完一根 bar 再一起打分,节奏是毫秒到秒。几十纳秒的交接看不出来,要盯的是一波数据进来以后,多久全部算完。
逐笔触发(做市、套利):一条行情就可能触发一笔报单。分片线程里一路算信号、过风控、发单,不过汇合、不过队列,全程在微秒级;每多一次跨核交接,就多几十纳秒和一份抖动。
1.4 旁路:慢了也不能拖住热路径
落盘、日志、监控、客户端推送都在旁路上:热线程只往环或队列里写一条就走,慢活交给别的线程或进程。旁路可以隔一会读一批,但要算好容量,别被套圈。交易员的指令(启停、撤单、改参数)方向相反,经服务进程走队列送进决策线程。
1.5 文章索引:每个框在哪篇讲过
| 框 | 文章 |
|---|---|
| 热线程绑核、网卡中断别打到热核 | #1 CPU 隔离与 NUMA |
| 跨核交接为什么要几十纳秒、伪共享 | #2 内存序与缓存行 · MESI 专题 · 速查 2.6 |
| 热路径不现分配内存(订单、消息对象) | #3 内存池与分配器 |
| 分片队列(SPSC)、异步日志 | #4 无锁队列与 Micro-Batching |
| 广播环、落盘读者被套圈、重启续读、什么时候不用广播环 | #5 SPMC 广播环 |
| 几个线程的订单汇到一个网关(多生产者队列)、旁路的阻塞队列、队列满了阻塞还是丢 | #18 线程安全队列 |
| 分片线程里一条行情的计算 | 缓存案例:一次行情重算 64 个期权 |
| 开盘前预热、账户快照的双缓冲 | #14 预取、预热与双缓冲 |
| 汇合、策略、风控、订单管理、下单网关 | 本页第 3 部分;以后的文章再补 |
2. 时间:每一段大概多久
这里的数都是帮助理解的量级,不对应哪套系统。标了“本站测过”的出自对应文章,其余是常见量级。
| 一段 | 大约多久 | 说明 |
|---|---|---|
| 交易所 → 本机网卡 | 托管机房直连:几 µs;经行情供应商转发:几 ms | 看离交易所多远、中间转几手 |
| 网卡 → 程序拿到包 | 绕过内核:约 1 µs;走内核协议栈:约 5–10 µs | 内核协议栈要过中断、拷贝、系统调用 |
| 解码一条行情 | 约 0.1–1 µs | 看格式 |
| 跨核交接一次(队列、环) | 约 20–100 ns | 一条缓存行从一个核搬到另一个核(速查 2.1) |
| 往广播环写一条 | 约 3–40 ns | 本站测过:全速写时,守在最新槽位上的读者越多越贵(#5 1.6) |
| 一条行情更新盘口和因子 | 几百 ns 到几 µs | 看因子多少;缓存命中差一层,就差好几倍 |
| 汇合 | 等最慢的那个分片 | 截面策略的节奏是 bar:毫秒到秒 |
| 策略和风控 | 逐笔判断:微秒级;几千只股票一起排名:约百 µs | 都在内存里算 |
| 报单到交易所、拿回回报 | 托管直连:往返几十到几百 µs;经普通券商柜台:到毫秒 | 网络和撮合,自己能优化的不多 |
| 推送到交易员屏幕 | 几十 ms | 给人看的,够用就行 |
| 写盘 | 毫秒级 | 所以只能放在旁路上 |
这张表说明两件事。一是几十纳秒的交接,放进毫秒级的链路里看不出来,只有整条链只有几微秒的系统才值得抠它。二是行情蜂拥的时候,慢的不是交接,是排队(3.5)。
3. 怎么切:工业级系统的几条规则
3.1 关键路径一个线程跑到底,队列只放旁路
最快的系统把收包、解码、更新盘口、判断、发单放在同一个线程里,一个函数接一个函数调下去,数据一直留在这个核的缓存里。每过一次线程之间的队列,就是一次跨核搬运,几十纳秒,外加一份抖动;所以队列只用来把落盘、日志、监控这些慢活甩出去。再快一档,是把解码甚至发单放进网卡上的 FPGA,软件只设参数。
3.2 多核靠按股票分片,片内跑到底
一个线程算不过来全市场,就按股票分给几个线程。同一只股票的消息必须按顺序处理,所以一只股票永远交给同一个线程;不同股票的线程并行算。要按消息量分,不按股票个数分:热门股的消息多得多,一般按前几天的消息量排一张“股票 → 线程”的表,开盘前定好。交易所的行情本来就按通道发,能直接按通道收,就不用自己再分一次;源头是一路全量的(比如供应商转发),才由接入线程按表分发。
3.3 一份数据给几个读者:广播环还是分开的 SPSC
读者都要全量、会增会减、在别的进程里(落盘、转发、监控),用广播环:写一份,生产者不看读者。读者各要一部分,或者丢了数据必须当场知道,用按读者分开的 SPSC。两者常叠着用:接入线程写一份进广播环,同时按股票写进各分片的队列(#5 1.7)。
3.4 汇合只放在必须汇合的地方,状态只让一个线程写
截面打分要等所有股票,所以有汇合点;单只股票自己的因子不用等别人。持仓、资金、在途单只让决策线程一个人改;推送、监控要看,就读它发布出来的快照,不碰正在改的那份。快照怎么发才不会读到一半新一半旧,见 #14 的双缓冲。
3.5 行情蜂拥:慢在排队,不在交接
开盘、收盘、消息面的时候,消息量是平时的好几倍。只要每条要做的活比消息到来的间隔长,就开始积压:后来的消息要等前面的算完,因子用的就是旧行情。过一次队列几十纳秒,跟排队比可以忽略;要省的是排队:按股票分给更多线程,或者让每条的活变少。
积压都堆在环和队列里:积压 =(到达速度 − 处理速度)× 持续时间,不能超过容量,广播环超过就会套圈(#5 2.3.2)。要知道够不够,拿真实行情把开盘那几分钟按原速放一遍,量每条从进来到算完的时间,再看积压怎么涨落。
3.6 旁路不许拖住热路径
热线程打日志、交给落盘,只往队列或环里写一条就走(#4);格式化、写盘、推送都在别的线程。不赶时间的读者不要死循环盯着最新槽位,隔一会读一批,省得每条都把生产者要写的那一行抢走一次(#5 1.6.5)。
3.7 回放用同一份代码
落盘的行情按原顺序喂回同一份因子代码,离线结果和盘中逐值对得上,才说明回放可信。改了代码也靠它做回归:同一天的行情,改前改后输出一个值都不变,才算没改坏。