物联网设备交互的一些实践经验
一、通断电控制必须避开现场使用的敏感时段远程拉闸/合闸看起来只是平台发个命令但对现场影响极大。如果在凌晨两三点把电断了现场的冷链设备、安防设备、存储设备可能都会受影响第二天发现问题后责任很难说清楚。反过来如果太早突然合闸有些设备启动电流冲击大也可能带来隐患。所以控制类命令必须限定在“出了问题有人能及时处理、现场也能接受”的时间段比如 8:00 到 22:00。这个窗口要可配置因为不同场所、不同季节的作息时间不一样不能写死。配置格式也要严格校验。像8-22可以但如果写成22-8或8--22不能静默用默认值跑要直接告警。否则半夜误发断电命令后果很严重。另外通断电、费率下发、时间同步这些都属于写操作需要受时间窗口约束召测读数据是读操作一般不受这个窗口限制。二、晚上统一召测表计只负责计量结算是平台的事表计本身不做结算也不负责费用计算它只是记录累计量、瞬时量、费率用量这些数据。真正的计量结算、报表统计、费用计算都是在平台侧完成的。所以每天晚上统一召测本质上是平台把分散在各个网关下面的用量数据收回来用于平台自己的日结、月结、能耗分析、计量结算。这也解释了为什么要按网关聚合。一个网关下面可能挂几十块表如果一块一块表去召测通信次数会爆炸。按网关打包后一次通信就能把下面所有表的用量数据带回来平台拿到后再自己处理。不过也要注意有些网关一次吃不了太多命令协议帧长度有限制。要设个上限超了就拆成几个批次别把网关撑死。三、三台机器一起跑定时任务必须选个老大计算服务部署了多个实例。一开始每台机器都定时扫任务表结果几台机器同时扫、同时入队同一批命令被发了好几次。设备那边收到重复命令有的直接报错有的执行了多次问题很严重。后来就加了个 Leader 选举。所有机器把自己的名字和心跳时间写进一个集合按名字排序排第一的就是老大。只有老大干活其他机器看着。心跳要持续刷新老大超过几个心跳周期没更新大家就重新选老大。这样高可用也有了重复下发也解决了。四、加机器扩容别让设备到处搬家后来设备量上来了要加机器。如果用简单的取模路由加一台机器后几乎所有设备的映射都变了原来机器上的连接缓存全失效大量网关要重新建连那段时间系统抖得厉害。后来改成一致性哈希。把网关编码映射到一个环上每台机器负责环上的一段。加机器时只有环上新增区间里的网关会搬家大部分网关还留在原机器上稳很多。不过也要盯着负载分布。有时候网关编码分布不均匀某台机器会特别忙。后来定期采样各机器负责的网关数量必要时调整虚拟节点数。五、任务进了队列消费者却找不到这问题特别阴。任务明细明明写进 Redis 了但消费者那边说没收到。排查后发现任务入了队但“这个网关归哪台机器处理”的路由信息没写进去。任务在 Redis 里躺着没人知道要去取它。后来入队时用 Lua 脚本把“写任务”和“写路由”打包成一个原子操作中间失败就整体回滚。另外还加了个兜底修复定时检查“哪个网关的任务队列里有东西但路由集合里没它”发现了就自动补进去并打告警日志。这种半入队的问题靠人一个个查太慢必须让系统自动修。六、新老系统并行任务不能丢也不能乱发系统升级那会儿新消费者监听新的机器队列老消费者还在监听老的全局队列。直接切到新队列老系统就漏任务一直写全局队列新系统又用不上。最后决定在兼容期双写同一个任务同时写进新机器队列和老全局队列。代价是可能重复处理所以消费端必须做幂等同一条任务要能识别出来重复的直接丢掉。双写只是过渡不是常态。计划里要定明确的下线时间到点就停掉老队列不能一直拖着两套机制。七、某块表老是被重复下发状态和分页都要查有段时间发现某块表频繁收到同样的命令。顺着查发现几个问题第一重试任务拉的状态不对。本来应该拉“失败待重试”的任务结果代码里拉的是“已成功”的任务成功的又被拖出来重发。第二分页用的 offset扫描过程中数据有变化同一条记录被扫到两次又入队一次。第三 队列本身没有去重同一个网关同一种命令可以无限入队。后来改成按“失败/超时/未下发”这些状态重试分页改用“上次最大编码”做游标避免跳行。同一网关同一类命令同一时段只允许一条在队里。八、网关一次只能串行执行但命令又分优先级这是最想单独拿出来说的。有些网关走的协议比较老命令发出去响应回来报文里没有序号或标识能把两者对上。你连续发两条命令回来两条响应根本不知道谁对应谁。所以这种网关下面同一时刻只能有一条命令在飞必须等响应回来了才能发下一条。但同一个网关下面任务又有急有缓远程拉闸合闸是急的费率下发可以等批量召测量大但时效性不强。这里最容易踩的坑是把优先级理解成“高优先级开更多线程并行跑”。对单个网关来说线程再多也没用协议层不支持并发。优先级正确的用法是当这个网关空闲时从它的任务里挑优先级高的先执行。我们的做法是按“网关 优先级”分队列但同一个网关的所有优先级队列都路由到同一台机器这样这台机器才能全局掌控“这个网关现在忙不忙”。机器内部维护一个集合记录“正在等响应的网关”只有不在这个集合里的网关才能取下一条任务。超时重试更要小心。如果响应超时了你重发一条结果第一条响应刚回来会被错当成第二条命令的回复。对没有对应关系的协议超时重试前最好把网关状态清干净必要时先断一下会话再重发宁肯慢一点也不要对错了号。另外单个网关串行不代表所有网关都串行。不同网关之间完全可以并行。并发控制粒度要精确到单个网关而不是一竿子限制整个系统。九、网关通信能力本身也是瓶颈有些网关不光不能并行单位时间内也处理不了太多命令。有段时间入队太快网关根本吃不过来队列越积越长Redis 内存一直涨。后来给每个网关加了速率限制消费端维护一个最近 N 秒内的命令计数超过阈值就暂停取新任务。这样平台侧不会把网关撑爆系统也稳多了。写在最后跟设备打交道这么多年最深的感受是设备不会按你的预期出牌。平台能做的不是让设备变聪明而是把“什么时候发、发给谁、由谁发、发失败怎么办、重复发怎么办、扩容了怎么办”这些不确定性都兜住。说穿了就是几个关键词时间窗口、按网关聚合、Leader 选举、两层队列、原子入队、一致性哈希、幂等、串行约束、速率限制、自动修复。落到自己系统里不用一次全做哪个痛先做哪个逐步补齐就好。