行情服务器收到交易所的流之后,面对的是一个和交易完全不同的扩展性问题:行情是只读广播,同一个数据要给所有人。这篇文章讲分发的两层核心设施——组播和行情代理——以及它们各自补对方什么短板。
单播的成本模型
先看不组播会怎样。服务器对每个订阅客户端维持一条 TCP 连接,每条行情复制一份发出去。发送成本和订阅数成正比:
$$ C_{\text{unicast}} = N_{\text{client}} \times B_{\text{stream}}, \qquad C_{\text{multicast}} = B_{\text{stream}} + \epsilon $$
$B_{\text{stream}}$ 全市场 Level-2 盘中峰值轻松过 1 Gbps。单播模式下 100 个内网终端就要 100 Gbps 的网卡和交换容量,物理上不可能。组播的语义是"发一份,网络设备负责复制":服务器把包发到组播组地址(224.0.0.0/4 段),订阅者通过 IGMP 报告加入组,复制发生在交换机上,离订阅者越近的节点复制次数越多,根链路上永远只有一份。
注意组播解决的是网络内的广播效率。它建立在 UDP 上,也就继承了 UDP 的全部缺陷:不保证到达、不保证顺序、没有连接状态。行情系统对丢包的容忍度比交易高——行情丢一条靠快照恢复,不需要可靠传输——所以"不可靠"在这里恰好是特性:可靠传输的 ACK 和重传窗口本身就是延迟和状态负担。
组播落地的三个工程细节
行情专网隔离。组播流量洪峰是均值的十倍,绝不能和交易、办公流量挤一张网。券商机房通常给行情划独立 VLAN、独立网卡甚至独立交换机,防止行情洪峰把交易包挤在队列里。监管侧对交易和行情的隔离也有明确要求,检查时会看物理或逻辑隔离的实证。
频道分片。交易所把全市场按证券切成几十个频道并行发送,接收端每个频道一个线程收包、一个序列号空间。分片既是带宽分摊,也是故障隔离:一个频道出问题不影响其他频道,解析器饿死在一个频道上不会拖垮全局。
TTL 与边界控制。组播包 TTL 设小(通常 1),把广播域限制在机房内,防止组播泄漏到广域网——运营商网络里的组播是另一套协议(PIM),券商自建广域网做双机房同步时才会碰。
行情代理:最后一公里的翻译器
组播到不了公网。手机 APP 和 PC 客户端在千家万户的 NAT 后面,运营商网络不转发组播,移动网络更是连接随时断。行情代理(行情网关、推送网关)就是夹在中间的翻译层:
- 协议转换:内网组播是私有二进制,公网端换成 TCP 长连接上的私有协议或 WebSocket/Protobuf,适配客户端的弱网环境;
- 订阅管理:客户端不可能订阅全市场(一部手机的带宽吃不下),代理维护"哪些客户端订了哪些证券",只推订阅内容,这正是单播语义回归的地方——但复制成本从行情服务器转移到了代理层;
- 流量整形:客户端网络是最大方差来源,代理做合并刷新(同一个证券 200ms 内的多次变动合成一帧推送)、差量压缩、慢客户端检测(写缓冲积压超过阈值就断开让它重连快照同步,而不是让积压无限涨拖垮代理)。
代理层本身是无状态可水平扩展的:不持有行情数据(数据在行情服务器共享内存里,代理只是读者),只持有连接和订阅关系。连接数被打满就加机器,订阅路由用一致性哈希按证券分配到不同代理实例,同一证券的订阅落在同一实例,合并刷新的收益才能吃满。
分层 fan-out 的成本账
完整分发树:交易所 → 专线 → 券商接收机 → 行情服务器(共享内存快照)→ 内网组播 → 行情代理集群 → 公网客户端。每往下一层,单位数据的成本递减、规模递增。行情服务器到代理之间仍可用组播(机房内网),代理到客户端是 N 条 TCP。关键设计原则是有状态的部分收敛在叶子:行情服务器和代理都可以随时重启,靠快照+序列号恢复,任何一层都不允许积累"丢了就补不回来"的状态。
容量估算要在代理层做最坏假设:客户端订阅行为高度同质——涨停板那天,所有人都在看同几只票。按"平均订阅数 × 在线人数 × 峰值行情速率"估代理出口带宽,再留一倍冗余,这是洪峰时代理不崩的生命线。《行情系统的容量与降级》会展开过载时的降级阶梯。