你有没有遇到过这种场景:物流信息明明更新了,页面上那个"运输中"的小卡车却一动不动?刷新一下,状态是对的,但实时推送就是没响。这不是网络断了——网络断了你会看到转圈圈。这是那种更安静的失败:后台一切都好,只是用户盯着看的那块屏幕没反应。
我在设计物流追踪网关时,被这个问题逼出了一个反直觉的决定:让WebSocket成为整个系统里唯一可以临时失效的部件。不是Redis,不是数据库,是那个用户直接感知的实时推送层。
![]()
这个选择听起来像偷懒,但它是整套架构的支点。
先看清边界在哪。DHL、DPD、GLS三家承运商的接口长得完全不一样。DHL用DHL-API-Key,事件藏在shipments[0].events里。DPD出错时可能直接吐HTML,所以DpdAdapter得先检查Content-Type才敢碰JSON。GLS更绝,日期是06.05.2026这种本地格式,时间还要按CET转UTC。
这些脏活全关在adapter层。一旦跨进核心系统,所有事件只有一个形状:承运商、追踪号、原始状态、标准化状态、时间戳,以及一个决定性的dedupKey。
这个key的生成逻辑极其朴素——把承运商、追踪号、原始状态、ISO时间戳四个值拼起来。但朴素的东西往往承重。承运商会重复发送同一扫描,轮询可能超时重试,进程重启后会重新拉取历史。如果事实没变,key就不变。
我最初以为实时性的复杂度会在WebSocket层:连接保活、心跳、订阅映射、广播性能。这些确实是真问题,但不是正确性的真问题。真正的硬仗是确保Redis永远不会意外成为真相来源。
事件处理器的流程很克制:先查重,再插入PostgreSQL,用onConflictDoNothing()做最后一道防线。两个worker同时查到"不存在"、同时尝试插入时,数据库的冲突处理会拦住重复。WebSocket扇出失败?没关系,数据库里的时间线是对的,客户端下次轮询或重连时会拿到完整状态。实时推送只是交付层,不是事实层。
这个设计让网关的大部分逻辑变简单了。轮询器不需要记住上次看到什么,adapter不需要各自维护去重缓存,WebSocket层不需要判断事件是否新鲜。处理器在数据库前挡一道,全局统一。
用户偶尔错过的那个动画,是系统允许丢失的。但那个永远不会错乱的物流时间线,是系统承诺守护的。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.