2. 行情接入层丢包与乱序导致重建失败
订单簿重建失败时,很多人第一反应是去查重建算法或撮合逻辑,但在这类故障中,真正的问题往往发生在更上游——行情接入层。交易所的增量行情通常以带序列号的 UDP 组播或 TCP 流下发,重建引擎依赖“序列号连续”这一前提来拼接增量。一旦接入层出现丢包或乱序,序列号出现缺口或倒序,重建引擎要么直接报错退出,要么在错误的状态上继续叠加,最终表现为订单簿深度错乱、价格档位缺失或成交量异常。本节要解决的核心问题是:如何证明问题出在接入层,以及如何用补包机制把数据完整性补回来。
先分清两种故障形态:丢包与乱序
丢包和乱序在现象上都表现为“序列号不连续”,但成因和处理方式不同,必须先区分。丢包是指某个序列号的报文根本没有到达应用层,序列号出现真实缺口,例如收到 1001、1002、1004,缺了 1003。乱序是指报文都到了,但到达顺序与序列号顺序不一致,例如 1001、1003、1002,此时序列号集合是完整的,只是顺序错了。前者需要补数据,后者只需要在应用层做重排序缓冲。很多团队把乱序误判为丢包,反复向交易所请求补包,既增加延迟又浪费带宽;也有团队把丢包当成乱序,缓冲区等不到缺失报文,最终超时丢弃,造成静默的数据缺口。
判断依据很直接:在接入层维护一个“已收到序列号集合”的滑动窗口,窗口内最大序列号与最小序列号之间的空洞,如果在一段合理等待时间(例如 50~200 毫秒,视行情频率而定)后仍未被填上,就是丢包;如果空洞在等待期内被后续到达的报文填上,就是乱序。这个等待时间就是重排序缓冲的深度,设置过短会把乱序误判为丢包,设置过长会引入额外延迟。
第一步:在网卡侧确认报文是否真的到达
应用层看不到的报文,未必没有到达网卡。排查的第一步是把观测点尽量前移,用抓包确认物理链路上到底有没有这个序列号的报文。常用做法是在行情接收服务器上执行:
tcpdump -i eth0 -s 0 -w market.pcap 'udp port 30001'
# 或针对组播行情
tcpdump -i eth0 -s 0 -w market.pcap 'dst 239.10.20.30 and udp'
抓包后按序列号字段过滤比对。如果 pcap 中存在缺失的序列号,说明报文已到达网卡,问题在网卡到应用层之间——常见原因是内核 socket 接收缓冲区溢出(netstat -su 中的 RcvbufErrors 或 packet receive errors 增长)、多队列 RSS 分发不均、或应用读取线程被阻塞。如果 pcap 中确实没有该序列号,说明报文在更上游就丢了,可能是交换机、光模块、组播路由或交易所侧发送问题,此时应用层补包是唯一补救手段。
-B 参数)并避免在业务高峰期用 -w 写盘拖慢 IO;生产环境更推荐用 PF_RING 或 AF_XDP 做零拷贝旁路采集,避免抓包本身成为新的丢包源。第二步:序列号比对,把“感觉丢包”变成“证据丢包”
抓包只能证明链路层的情况,应用层还需要一套持续的序列号校验。推荐在接入线程中为每个行情通道维护一个期望序列号 expected_seq,每收到一帧就比对:等于期望值则正常推进;大于期望值说明中间有缺口,进入等待补包状态并记录缺口区间;小于期望值说明是重复或迟到报文,直接丢弃。缺口区间要落盘或打点,形成可回溯的完整性日志,例如记录 channel=A, gap=[1003,1003], first_seen=12:00:01.230, filled=12:00:01.245。有了这份日志,才能统计丢包率、乱序率和补包成功率,而不是靠“重建偶尔失败”这种模糊描述。
第三步:补包机制怎么设计才可靠
确认丢包后,补包是恢复完整性的关键。主流交易所通常提供两种补数据手段:一是增量重传接口,按序列号区间请求重发;二是快照加增量接口,直接拉取某一时刻的完整订单簿快照,再从快照序列号之后继续拼接增量。前者延迟低、数据量小,适合零星缺口;后者数据量大但能彻底修复状态,适合缺口过大或重建状态已不可信的情况。
工程上建议设置分级策略:缺口长度小于阈值(例如 20 个报文)且补包接口可用时,走增量重传,并在重排序缓冲中保留后续报文等待回填;缺口超过阈值、或补包请求连续失败、或缺口跨越了快照周期,则放弃增量修补,直接拉取快照重建。这里有一个容易踩的坑:补包回填的报文序列号可能早于当前已处理位置,接入层必须支持“回填到历史位置”而不是简单追加,否则重建引擎会收到倒序增量而报错。实现上可以用一个按序列号索引的待处理队列,回填报文插入对应位置,由消费线程按序取出。
rmem_max、netdev_max_backlog)和交换机端口错包计数,从根因上降低丢包率。修复后的验证与观测指标
修复是否生效,不能只看“重建不再报错”,而要看完整性指标是否收敛。建议在接入层持续输出以下观测项:每通道的累计收包数、缺口数、乱序数、补包请求数、补包成功率、平均缺口持续时间,以及重建引擎的序列号连续性校验结果。一个健康的接入层,补包成功率应接近 100%,平均缺口持续时间在毫秒级,且缺口数与交易所侧发送量之比处于极低水平。把这些指标接入监控告警,当缺口率突增或补包成功率下降时立即介入,才能把“偶发重建失败”变成可观测、可定位、可预防的常规运维事件。
回到本节的主线:重建失败只是表象,接入层的数据完整性才是根因所在。先用抓包把观测点前移到网卡,区分“报文没到”和“报文到了但应用没收到”;再用序列号比对把模糊的丢包感觉变成带区间的证据;最后用分级补包策略把缺口补回,并用完整性指标验证修复效果。这条链路走通,接入层丢包与乱序导致的订单簿重建失败就有了系统性的定位与处置方法。