ELI5 · 说人话

怎么接一百种机器人

MQTT · JSON TCP · 私有二进制 HTTP · XML 回调 平台 统一翻译 上层业务只说一句 去 302 送快递 多协议接入 · 统一业务接口

机器人厂商各说各的方言。平台干的活只有一件:把一百种方言,翻译成一种普通话,让上面的业务系统一辈子只用学这一种。

先看为什么

不做平台,线会打成结

各接各的 4 × 3 = 12 条 真实是 10 × 5 = 50 条 都接平台 4 + 3 = 7 条 真实是 10 + 5 = 15 条

机器人从 10 种涨到 100 种:左边从 50 条变 500 条,右边从 15 条变 105 条。乘法变加法,这就是中间那一层唯一存在的理由。

整体长什么样

中间夹四层,各管一件事

1 接入层 MQTT / WebSocket / TCP 都在这里终结 · mTLS 一机一密 2 适配层 一家厂商一个插件 · 私有报文 → 标准消息 3 能力层 统一能力模型 · 设备影子 · 指令状态机 · 任务调度 4 业务 API REST 下指令 · 事件流收结果 · 契约版本化,只增不改 下行 上行 调度大屏 工单系统 运营后台

分层的检验标准只有一条:接第 101 家厂商时,只有第 2 层要动。1、3、4 层一行代码都不该改。

怎么加第 101 家

厂商是插头,内核是插座

平台内核 只认标准消息,永远不认识任何一家厂商 厂商 A MQTT · JSON 厂商 B TCP · 二进制 厂商 C HTTP · XML 第 101 家 照着插就行

插件只需要交出三样东西,内核凭这三样就能驱动任何机器人:

decode(厂商报文) → 标准事件
encode(标准指令) → 厂商报文
capabilities() → 这台机器能干哪些活
隔离
插件跑在自己的沙箱里

一个厂商的解析代码抛异常、死循环、内存泄漏,不能把整个网关拖死。独立进程或独立线程池,超时就掐。

声明
能力靠声明,不靠 if-else

内核里出现 if (vendor == "A") 的那天,插件化就死了。差异全部下沉到插件的 capabilities()

热插
上新厂商不重启

插件按版本注册到插件仓库,网关动态加载。新增一家不该影响已经在线的 10 万台老设备。

兜底
解析失败要留原始报文

插件解不出来的字节,原样存进死信队列。不然厂商改了个字段,你连排查的证据都没有。

说同一种话

长得再不一样,也得报一份能力清单

扫地机 0x01 GO · 0x07 SUCK 配送车 {"cmd":"nav"} · door.open 统一能力清单 move · work · door · battery

「移动」这件事,一百种机器人都会。那它就该只有一个名字 move(x, y)。剩下的差异用四件套描述:

capability
能力:它会干哪一类事

移动、清洁、载货、开舱门、拍照。业务先问「你会不会」,再决定派不派活给它。

command
指令:你让它干(下行)

cmdId、参数、超时。指令是请求,不是结果——发出去不等于做完了。

event
事件:它告诉你(上行)

到点了、卡住了、电量低了、任务完成了。终态只能由事件确认,云端不许自己猜。

property
属性:它现在什么样

位置、电量、载荷、固件版本。属性会变,所以要存在「影子」里,还要带时间戳。

云里的替身

业务问影子,不问机器人

业务 要看状态 读影子 云端 · 设备影子 替身 desired 我要你变成的样子 业务写进来的期望 reported 你说你现在的样子 机器人自己报上来的 下发 上报 真机器人 影子永远是二手消息,比机器人慢一点点——所以后面要对账。

有了影子,业务查 10 万台设备的状态就是查一次 Redis,不用去戳 10 万条长连接。机器人离线时,影子还在,只是多了一个 offline 标记和最后一次上报时间

影子怎么设计?三段状态 · 逐字段时间戳 · 收敛闭环 →

还活着吗

心跳漏三拍,才算走了

1 每 30 秒报平安 TCP 连着 ≠ 活着 2 漏一拍先别慌 ? 网络会抖,一次不算数 3 漏三拍,判离线 标记 + 立刻广播事件
device.offline → 事件总线 → 所有订阅方 200ms 内收到

离线要让业务「快速感知」,靠的不是让业务每秒来问一次,而是平台把事件推出去。10 万台设备被轮询,能把数据库打穿;被订阅,只是多了一条 Kafka 消息。

一条指令的一生

超时,不等于没做

已下发 已投递 已 ACK 执行中 已完成 3 秒没回执 超时,没等到 ACK 同 cmdId 重发 · 设备侧去重 ×3 重发 3 次仍无音 状态 = 不确定,交给对账
机器人可能早就把活干完了,只是回执在路上丢了。
所以第三种结局不叫「失败」,叫「不知道」。

敢重试的前提是幂等。幂等靠这四条同时成立:

cmdId
重发必须带同一个号

机器人侧记住最近执行过的 N 个 cmdId,见到重复的直接回上次的结果,不再执行第二遍。

串行
同一台机器不并发下发

指令按 deviceId 分区,一台设备一条队。不然「前进」和「停止」谁先到都不确定。

退避
重试要有上限

1s / 2s / 4s,最多 3 次。无限重试会在机器人恢复的瞬间把它砸死,这叫重试风暴。

终态
完成由机器人说了算

ACK 只证明「收到了」。真正的完成必须等一条 task.finished 事件,云端不许自行改成完成。

断了怎么接回来

退着敲门,别一起敲

1s 2s 4s 8s 16s 30s 30s 第 n 次重连等多久 · 30 秒封顶 · 再叠一个随机抖动

机房抖一下,10 万台设备同时掉线。如果它们都等 1 秒后重连,你会在第 1 秒被自己的设备 DDoS。抖动就是给每台机器发一个不一样的号。重连回来之后还有三件事:

对不上怎么办

永远以机器人说的为准

云端影子和真机不一致是常态,不是故障。你要做的不是消灭它,是让它在几秒内自己收敛。三层兜底,越往下越慢也越彻底:

上线时
全量报一次

设备一连上,先把完整状态整个交出来。这一次覆盖,能解决掉大部分不一致。

心跳时
带一个状态指纹

每 30 秒的心跳里捎上状态 hash。云端一比对,不一样就主动拉一次全量。代价只有几个字节。

定时
后台慢慢巡检

按分区扫描,几分钟一轮,专门捞那些「不确定」的指令和长期没动过的影子。兜最后一层底。

冲突裁决只有一条规矩:reported 覆盖 desired,机器人赢。

谁去干这活

一台机器人,一条队

任务池 P0 送 302 P1 巡逻 3F P2 回充电桩 P1 送 508 调度器 ① 按优先级排队 ② 就近派给谁 ③ 高优可抢占 正在送 302 空闲 正在巡 3F 一台机器人 = 一条串行队列,所以指令天生要按 deviceId 分区。

调度器和指令下发是两件事:调度决定「派给谁」,下发负责「送到并确认」。把它们揉在一起,抢占任务时你会同时改两台机器人的状态,然后就再也说不清楚了。

从 10 万到 100 万

按设备号切块,加机器就行

100 万台设备 分区 = hash(deviceId) % N 分区 1 ≈ 25 万连接 分区 2 ≈ 25 万连接 分区 3 ≈ 25 万连接 分区 N 不够就再加 同一台设备永远落同一个分区,于是它的消息天然有序、天然串行。
无状态
网关只管收发

连接状态存 Redis,消息进 Kafka。网关节点挂了随便重启,设备退避重连后落到别的节点也一样能用。

分区
顺序性是切出来的

不用全局锁,也不用分布式事务。按 deviceId 分区之后,单台设备的有序性是免费的。

影子
100 万份状态并不大

一台设备的影子约 2KB,100 万台也就 2GB 左右。一个 Redis 集群装得下,查询是 O(1)。

削峰
上报走事件流

早高峰 100 万台同时上报,直连数据库必炸。中间垫一层 Kafka,业务方各自按能力消费。

让 AI 来指挥

Agent 只能走这道闸

Agent 白名单 只开该开的门 参数校验 越界直接拒 二次确认 危险动作要人点头 机器人 每一次调用都落审计:谁 · 何时 · 哪台 · 干了什么 · 结果如何 出事能回放,这比什么都重要

Agent 不该拿到设备的原始协议,甚至不该知道设备是哪家厂商的。它只能看见能力网关暴露出来的那几个动词,而且每个动词都带着参数范围、批量上限和权限标签。「把这层楼所有车召回」——先看 Agent 有没有 fleet.recall,再看批量上限是不是被超了。

红线

这六个坑,一定会踩

云端显示在线,机器人其实早就死机了

TCP 连着不代表进程活着。只信业务层心跳,漏三拍判离线;连接断开只是提前触发,不是唯一依据。

重试把同一趟任务发了三遍,机器人跑了三趟

重发必须复用同一个 cmdId去重放在设备侧。云端做不到真正的幂等——你永远不知道那条丢的是请求还是回执。

接第 11 家厂商,改了内核代码

内核里出现厂商名就是红线。差异全部关在插件里,插件之外的代码不认识任何一个品牌。

机器人离线了,业务半分钟后才发现

别让业务轮询。判定离线的那一刻把事件推进总线,订阅方毫秒级收到,顺手把在途指令标成不确定。

指令超时就标失败,结果机器人其实做完了

超时只能标「不确定」。终态由机器人上报的事件确认,或者等对账收敛。云端擅自改状态,业务就会拿着假数据做决策。

Agent 一句话把整层楼的车全召回了

能力白名单 + 批量上限 + 危险动作二次确认,三样缺一不可。再加全链路审计,出事至少能查清是谁下的。

厂商说什么,是插件的事;
业务听到的,永远只有一种说法