命令发送成功,数据却没回来:一次设备同步故障与补录设计
平台返回发送成功,并不代表设备数据已经同步成功。沿着命令、回程消息和时间窗口拆开排查,才能解释为什么定时任务恢复之后,历史记录仍然没有回来。
🎧 文章导读
先把“成功”拆成三层
这次故障最容易误判的地方,是日志里同时存在两个看起来矛盾的结果:平台返回 SENDMESSAGE_SUCCESS,定时同步却不断失败,业务层最后只剩下一句“接口调用失败![null]”。
命令被平台受理、设备执行后返回、业务数据成功入库,是三个不同的结果。 如果把第一层当成第三层,后面的排查方向就会全部偏掉。
实际链路是:调度任务每 2 分钟调用业务服务;集成服务向平台发命令,拿到 commandId;平台将命令下发设备,再把设备回复通过消息通道推回来;原请求线程按命令编号等待,最长约 15 秒。
图 1:去程受理成功,只覆盖了链路前半段。故障需要沿回程消息继续定位。
| 信号 | 能说明什么 | 还不能说明什么 |
|---|---|---|
SENDMESSAGE_SUCCESS |
平台受理了命令 | 设备已回复、数据已入库 |
RECEIVE_FAIL 超时 |
等待期内未取得回复 | 一定是设备故障 |
| 收到成功回复、列表为空 | 本次查询正常且无记录 | 历史窗口之外也无记录 |
| 业务入库成功 | 数据通过了本系统校验 | 所有历史缺口均已补齐 |
这里的 15 秒等待还是自旋实现。它值得另行优化,但这次记录没有完成该项改造,不能把性能债务和本次回程故障混为一个根因。
沿证据找断点,而不是沿报错文案猜
业务日志中的 [null] 实际上丢失了集成服务返回的错误信封。继续向下查,才能看到“命令已发出,但没有在预期时间内收到回复”。
设备是否有问题,还要换一个观察位置。平台管理台里,设备在线,查询能够很快返回。这将怀疑范围收敛到平台向业务系统投递回复的环节。另有网络不可达日志,结合故障时间,指向平台与系统之间的网络或投递异常;这些证据支持排查方向,但不足以进一步断言某台网络设备就是根因。
随后按时间压缩连续成功/失败状态,规律变得清楚:大部分夜间中断都与设备下班关机、次日开机一致,只有一个工作日白天出现异常长时间中断。规律比单条 ERROR 更有解释力。
下面是适合记录在排障笔记里的脱敏时间线格式:
1 | 08:00—17:00 正常同步 |
这次还发现另一类设备业务的无限重试造成大量日志。它是独立问题,应保留线索单独处理;不能因为日志数量最多,就把它当成访客同步故障的直接原因。
两分钟执行一次,为什么仍然漏数据
定时任务每次拉取过去 2 小时的记录。正常情况下,一条登记大约有 60 次被查询到的机会,因此短暂网络抖动通常不会造成永久漏拉。
但这个冗余只存在于有限窗口内。一旦失败持续超过窗口宽度,恢复后的任务会继续向前走,不会自动回头。 “任务恢复”与“历史数据完整”因此没有必然联系。
图 2:窗口不断前移;故障恢复时,旧登记已经落在窗口之外。
可以用一个简单条件理解风险:
1 | # 这是风险判断示意,不是实际调度实现。 |
其中第二个条件很重要。设备夜间关机但无人登记,和白天有人登记却回程断链,补录价值完全不同。最有力的确认方法,是直接向设备查询故障时段,先证明源数据还在,再讨论如何恢复。
补录入口必须同时考虑兼容、幂等和校验
这次采用的修复比较克制:给既有同步接口增加可选的 startTime、endTime。不传参数时仍然查询过去 2 小时;传参时必须成对提供,格式错误直接拒绝。
以下是脱敏后的调用示意;路径和端口需替换为实际环境,接口仍应置于现有认证与运维访问控制下:
1 | curl --request POST \ |
宽窗口会重复拉到已入库记录,因此必须核对已有幂等逻辑。本次实现按设备记录标识判重,重复拉取会跳过已同步记录。推广到多设备系统时,还要检查这个标识是否只在单设备内唯一;如果是,就需要带上设备维度,不能照搬一个全局唯一假设。
补录还有第二道门槛:设备允许保存的数据,不一定通过业务系统校验。本次就遇到设备侧证件字段宽松、系统侧格式与校验位严格的问题。记录拉到了,但被业务校验拒绝,仍然不能算恢复成功。
因此,补录结果至少要分开记录:
| 结果 | 后续处理 |
|---|---|
| 已存在 | 幂等跳过,计入重复数 |
| 新增成功 | 核对业务列表及关联字段 |
| 字段不合法 | 保留失败原因,按业务规则修正源数据后重试 |
| 请求或回程失败 | 保留窗口与命令编号,重新确认链路 |
“校验失败就清空字段继续入库”属于数据语义变更,本次没有擅自采用。自动降级是否可接受,应由业务规则决定。
日志和发布过程也是修复的一部分
这次代码同时修复了错误透传。下层没有业务响应时,保留信封中的错误码和说明,避免在上层只剩下 null。理想的日志结构可以参考下面的字段;它是建议格式,不代表本次已全部实现:
1 | { |
修复过程还暴露了一个发布陷阱:本地代码打包部署后,没有及时进入团队统一构建来源,随后被另一轮正常发布覆盖。新接口一度又回到旧行为,宽窗口参数没有产生预期效果。
遇到“刚修好又不生效”,应该先核对部署包对应提交、文件摘要和实际版本,再怀疑数据被删除。堆栈行号可以辅助识别,但不应替代版本标识。
据原始记录,本次新增的控制器、错误透传和提供方相关测试共 6 条通过;补录曾完成一条合法样本的端到端验证。这些是当时的工程记录,本篇整理没有重新操作设备,也没有把后续待发布事项写成已完成。
这次最值得留下的经验是:同步故障要同时回答“链路恢复了吗”和“数据补齐了吗”。 前者靠分层观测,后者靠明确窗口、幂等恢复以及逐条业务结果。
延伸阅读
- 把 WebSocket 长期密钥移出前端:短期票据改造与上线闭环
- 从一条告警到真正闭环:规则、站内信、工单与权限的整合实践
- 8.7 秒到 231 毫秒:保留统计口径的 MySQL 窗口函数优化
- 上传上限放到 1GB,为什么仍然 OOM:附件复用与流式传输实践
本文根据 2026 年 9 月 22 日至 10 月 22 日的工程笔记整理。账号、凭证、地址和业务样本已脱敏;通用示例已在正文标明,历史验证结果以原记录为准。