4. 单笔丢包补录实战
逐笔数据丢包补录,最怕的不是“补不进去”,而是“补错了还不知道”。本节用一个完整的单笔案例,把日志定位、请求构造、结果验证、归档留痕四件事串成一条可复现的链路。案例中的证券代码、时间戳、序列号均为教学示例,用于说明判断逻辑,不代表真实生产数据。
一、案例背景与初始现象
某券商行情接入节点在日终对账时发现:某只沪市股票在 14:32:07 这一秒内,逐笔成交明细的本地序列号出现跳变。上游推送的序列号为 20240612-143207-000381 与 20240612-143207-000383,中间缺少 ...000382。下游策略服务反馈该秒成交笔数比交易所公开汇总少 1 笔,但价格与总量差异极小,属于典型的“单笔丢包”。
这类问题的关键特征是:缺口只有一笔、序列号连续区间内出现单点空洞、前后记录均正常。它既不是整段断流,也不是乱序,因此排查重点应放在“定位这一笔到底丢在哪一环”,而不是盲目重连或全量重拉。
二、从日志定位缺失记录
定位阶段建议按“先定缺口、再定环节、后定证据”的顺序推进。首先在接入层日志中检索缺失序列号的前后邻居,确认缺口确实存在于本地落库环节,而非上游未推送。示例日志片段如下:
2024-06-12 14:32:07.118 INFO recv seq=20240612-143207-000381 code=600XXX price=10.52 vol=300
2024-06-12 14:32:07.121 WARN buffer.overflow drop_candidate=1 queue=md_tick depth=8192
2024-06-12 14:32:07.126 INFO recv seq=20240612-143207-000383 code=600XXX price=10.53 vol=200
2024-06-12 14:32:07.130 INFO persist batch=88213 rows=2 gap_detected=000382
这段日志给出了三个关键证据:一是 buffer.overflow 告警出现在缺口前后,说明接收缓冲区在瞬时高水位时发生过丢弃;二是 gap_detected=000382 由落库环节主动识别,证明缺口判断逻辑生效;三是前后两条记录的价格、成交量均正常,说明链路未整体中断。由此可初步判定:丢包发生在接收缓冲到落库之间,属于本地处理环节的瞬时溢出,而非上游漏发。
进一步应核对三个位置:接收队列深度监控、落库批次号与行数、以及上游原始报文的本地镜像。若镜像中存在 000382 而落库缺失,则确认是本地丢弃;若镜像中也没有,则需转向上游链路排查。本案例中镜像存在该笔,故定位结论为本地缓冲区溢出导致单笔丢失。
三、构造补录请求
补录不是“把缺的那笔随便塞进去”,而是要保证补录记录与原序列在序列号、时间戳、业务字段上完全对齐,且不破坏下游的幂等与去重逻辑。构造请求前先明确补录来源:优先使用上游原始报文镜像,其次使用交易所历史查询接口回补,最后才考虑人工构造。本案例使用镜像报文,字段如下表所示。
| 字段 | 示例值 | 说明 |
|---|---|---|
| seq | 20240612-143207-000382 | 缺失序列号,必须与前后连续 |
| code | 600XXX | 证券代码 |
| trade_time | 14:32:07.120 | 成交时间,落在前后两笔之间 |
| price | 10.52 | 成交价 |
| volume | 100 | 成交量(股) |
| side | B | 主动方向 |
| source | mirror_replay | 补录来源标识,便于审计 |
补录请求应携带幂等键,通常由 seq + code + trade_time 组合生成,避免重复补录造成双写。示例请求体如下:
{
"idempotent_key": "20240612-143207-000382|600XXX|143207120",
"seq": "20240612-143207-000382",
"code": "600XXX",
"trade_time": "2024-06-12 14:32:07.120",
"price": 10.52,
"volume": 100,
"side": "B",
"source": "mirror_replay",
"operator": "ops_zhang",
"reason": "buffer_overflow_single_drop"
}
构造时有两个易错点:一是时间戳精度必须与前后记录一致,若原链路为毫秒而补录写成秒,会导致下游排序错乱;二是 source 与 reason 必须如实填写,它们是后续归档与复盘的关键依据,不能省略或伪造。
四、验证补录结果
补录提交后,验证要覆盖“单笔正确、区间连续、下游一致”三个层次。单笔正确指字段值与镜像报文逐项比对一致;区间连续指补录后该秒内序列号无空洞、无重复;下游一致指策略服务收到的成交笔数与交易所汇总对齐。示例验证查询如下:
-- 检查该秒序列号连续性
SELECT seq, code, trade_time, price, volume
FROM tick_trade
WHERE code='600XXX'
AND trade_time BETWEEN '2024-06-12 14:32:07.000' AND '2024-06-12 14:32:07.999'
ORDER BY seq;
-- 检查是否存在重复幂等键
SELECT idempotent_key, COUNT(*) AS cnt
FROM tick_trade
WHERE idempotent_key='20240612-143207-000382|600XXX|143207120'
GROUP BY idempotent_key;
预期结果是:区间内序列号从 ...000381 到 ...000383 连续,补录记录仅出现 1 次,且价格、成交量与镜像一致。若出现重复,说明幂等键未生效,需回滚后重新提交;若序列号仍不连续,说明补录写入的排序字段有误,应检查时间戳精度与排序规则。
五、归档与留痕
补录完成后必须归档,否则下一次对账仍会被判为异常,且无法追溯责任。归档内容至少包括:缺失序列号、定位证据(日志行号或截图索引)、补录请求原文、验证查询结果、操作人与时间、以及根因归类。本案例的归档记录可整理为一条工单摘要:
- 缺口:
20240612-143207-000382,单笔成交,价格 10.52,量 100 股; - 根因:接收缓冲区瞬时溢出,
md_tick队列深度达 8192 触发丢弃; - 补录来源:上游原始报文镜像回放,幂等键已校验唯一;
- 验证结论:区间序列号连续、无重复、下游笔数对齐;
- 后续动作:将该节点缓冲区上限从 8192 调整为 16384,并增加溢出即告警。
归档不是终点,而是把“这一次怎么修的”转化为“下一次怎么防”。本案例中,缓冲区扩容与溢出告警属于根因治理,应与补录工单关联,避免同类单笔丢包反复出现。
把这条链路走完,单笔丢包补录才算真正闭环:日志给出缺口与环节,镜像提供可信数据,幂等键保证不重写,验证确认区间与下游一致,归档把个案沉淀为治理动作。下一次再遇到序列号单点空洞,按同样的证据顺序推进即可,不必从重连开始试错。