行情系统的需求听起来简单:把成交价和盘口快点、稳点地送到每个客户端。但拆开看,"快"和"稳"在链路的每一跳是不同指标,且互相拉扯——压缩省带宽就增加解码延迟,组播提效率就丢了可靠性语义。这篇沿链路从交易所走到客户端,逐跳讲清在传什么、花多少时间、坏起来是什么样。
第一跳:撮合到行情网关(交易所机房内)
撮合引擎每产生一笔成交,把行情数据写入行情网关的发布缓冲区。这一跳的耗时是微秒级的,但有个关键设计:行情网关和撮合是解耦的。撮合不能因为行情发布慢而阻塞——成交先落内存队列,网关异步聚合打包。解耦的代价是行情有微小滞后(毫秒级),交易所刻意接受这个滞后来保撮合的确定性。
打包策略在源头就定下了整条链路的语义。快照模式(每隔固定间隔发一次全量盘口)和增量模式(每发生变动发差异)的选择影响下游一切:增量省带宽、延迟低,但丢一包就要靠序列号检测加重传或等下一张快照恢复;快照自恢复但带宽大、延迟高。沪深交易所的 Level-2 行情是快照加增量结合,盘中发增量,定期和变动时发快照兜底。包内是二进制定长结构——行情从来不用 JSON,原因很实际:每微秒的解析成本在十万级客户端上要乘十万倍,二进制流用指针强转就能读,JSON 还要建对象树。
第二、三跳:专线传输与券商接收机
行情从交易所机房到券商,主流是地面专线(SDH/以太网专线),老系统和偏远地区用卫星补盲。专线的特点是带宽固定、延迟稳定(同城 1~3ms),设计上按峰值带宽预留——开盘集合竞价后的行情洪峰是均值的十倍,带宽按洪峰买,闲置是常态成本。
券商机房的接收机(前置机)做三件事:解码二进制流、校验序列号、把行情写入本地行情服务器的共享内存。序列号校验是可靠性的第一道关:UDP 传输不保证到达,发现序列号空洞就触发补包(向交易所网关请求重传,有严格窗口)或直接等下一张快照覆盖。接收机本身要主备双活,因为从这里开始行情就是券商内部数据的唯一权威来源——接收机挂了,下游全部断粮。
第四跳:行情服务器:快照合并与派发
行情服务器把接收机喂的原始流整理成下游能用的形态:最新盘口快照区(全市场证券的最新十档、最新成交价,放共享内存,按证券 ID 定址)、增量广播队列(变化了的消息按序发出)。这一层的核心数据结构就是《盘口(十档)数据结构》要展开的快照加增量合并算法——客户端不可能每次收全量,只能在本地维护快照、按增量更新,乱序和重复都在本地消化。
派发方式在这里分叉。组播是主流:行情服务器把增量包发到一个组播组,所有订阅终端同时收到,发送成本和订阅数无关——这是十万客户端同时收到的唯一可行方式,单播发十万份连万兆网卡都打不满。行情代理解决组播到不了的最后一公里:公网客户端收不了组播,APP 服务器用 TCP 长连接推给客户端,代理层做协议转换、订阅管理和流量整形。
第五跳之后:客户端与端到端预算
汇总一条典型链路的延迟预算:
$$ T_{\text{total}} = T_{\text{撮合→网关}} + T_{\text{专线}} + T_{\text{接收机}} + T_{\text{服务器}} + T_{\text{组播}} + T_{\text{代理}} + T_{\text{客户端网络}} $$
前四项在券商机房内和专线里,合计通常是 5~20ms 量级(取决于交易所到券商的距离和服务器处理);最后两项的方差最大——客户端的 Wi-Fi、4G、跨网延迟轻松吃掉几十上百毫秒。所以"行情快不快"的感知,对终端用户而言大半不由券商决定,这也是极速行情产品的商业逻辑:收费卖的不是行情内容(那是交易所授权数据,内容本身免费或低价),是机房位置、链路质量和本地处理能力的组合——把券商接收机托管到交易所同城机房,第二跳砍到亚毫秒,这是专线延迟竞争的全部内容。
异常语义也要按跳定义:断线重连(每跳都要有心跳和重连后快照同步)、乱序(序列号检测,本地缓存乱序窗口)、重复(幂等处理,按序列号去重)。这些在《行情异常处理》里展开,链路总览只需要一个原则:每一跳的输出契约要自描述(带证券代码、序列号、时间戳),下游才能不依赖上游的内部状态独立恢复。