2026年的机器人与人是否会共存于同一座城市,现在很难下结论。但从技术投入方向看,“Robocity”已经从一句愿景式口号,变成一个值得认真拆解的工程命题。所谓 Robocity,可以理解为一个面向城市级机器人接入、调度、仿真和运维的云边端平台形态,它不等于某款机器人本体,也不等于某个地图引擎或调度后台。作为开发者,真正要先去想清楚的是:让几十台机器人同时在线,已经不再是一台机器人上的算法问题,而是后端平台、消息链路、任务编排、仿真回放和安全策略共同作用后的系统问题。这篇文章会把 Robocity 当作一条技术主线,帮助机器人工程师、后端开发以及正在准备多机 POC 的团队,搭出一个可以继续演进的极简城市级机器人平台雏形。
1. 先站在技术视角理解 Robocity:它不是单机项目,而是机器人城市操作系统
1.1 Robocity 的真实含义容易产生歧义
网上讨论 Robocity 的时候,常见误区是把“机器人能导航、能识别、能抓取”等同于“城市级机器人平台已经建成”。如果放到工程语境里,Robocity 并不是某个已经标准化的开源项目名称,它更像一套综合性的技术目标:机器人本体、边缘算力、中心云服务、数字孪生环境以及城市业务流程,必须被同一个技术底座连接起来。
把这个目标拆开看,至少包含几个技术议题:
- 多台异构机器人的统一接入与身份管理。
- 高频状态数据的上云、存储与回放。
- 指令下发、执行回执与任务状态机。
- 仿真环境和真实环境的接口一致性。
- 运营后台、地图服务和业务系统的结合。
如果只完成其中某一项,还称不上 Robocity。真正困难的往往是多个模块组合以后出现的边界问题。比如机器人上报状态正常,但任务平台不下发指令,这类问题通常不是某一个模块坏了,而是链路里的协议、权限、回执和调度规则没有对齐。
1.2 用五层结构定位 Robocity 的工程范围
为了避免一开始就陷入具体的 ROS 包或传感器选型,建议把 Robocity 的所有技术工作压进五层结构里。这五层分别是终端接入层、连接编排层、业务服务层、仿真数据层和运维安全层。
- 终端接入层:机器人、充电桩、门禁、梯控、机械臂等设备。
- 连接编排层:MQTT、gRPC、HTTP、任务队列,负责消息投递和指令路由。
- 业务服务层:地图管理、作业派单、机器人调度、计费和用户端接口。
- 仿真数据层:场景回放、算法评测、虚拟地图和日志数据集。
- 运维安全层:设备权限、网络隔离、监控告警、版本发布和故障回滚。
这套分层与 Web 项目常见的“前后端分离”不同。Web 平台主要处理请求和响应,而 Robocity 面对的是持续产生状态流、且可能断线、移动、碰撞和电量耗尽的物理设备。状态流一旦中断,后台必须靠超时和链路检测做出判断,而不能再沿用“用户刷新页面重试”的思维方式。
1.3 单机机器人和平台化机器人建设的差异
很多团队在演示中已经把机器人调得很稳定,但进入 Robocity 场景后,第一反应往往是“算法是不是不够强”,实际上更常见的问题是平台化能力不足。下面这张表能快速看出两类工作评估标准的差异。
| 关注点 | 单机机器人 Demo | Robocity 平台形态 |
|---|---|---|
| 核心目标 | 完成移动、识别、抓取任务 | 多机器人稳定接入、可靠调度、可重复验证 |
| 数据频率 | 本地日志为主 | 高频遥测上云、长期存储 |
| 消息可靠性 | 偶发丢包可接受 | 需要 QoS、回执和超时重试 |
| 指令来源 | 人工下发 | 调度引擎自动决策 |
| 地图更新 | 单机地图 | 多机共享地图和版本化地图 |
| 故障处理 | 重启或人工介入 | 自动隔离、切换备机、回滚策略 |
| 安全边界 | 实验室网络 | 设备证书、Topic 权限、网络分区 |
从表中可以看出,Robocity 的难点不是“更聪明的机器人”,而是“更稳定的机器人基础设施”。在单机场景中,状态偶尔丢失不影响演示;在多机协作场景中,一条丢失的指令或一次重复下发,都可能导致物理设备做出错误动作。
1.4 为什么 2026 年仍然只是序幕
把 2026 年当作序幕,而不是终点,是因为真正影响 Robocity 落地的技术条件还在快速变化。模型能力、芯片算力、传感器成本、5G/算力网络和云原生技术逐步成熟,会给大规模的机器人场景提供更好的基础,但距离城市级全天候运行还差几层能力:
- 城市级高精地图的更新和共享机制。
- 机器人跨厂家、跨品牌的互操作标准。
- 物理安全责任边界的软件化表达。
- 高并发实时调度下的稳定性验证。
因此,现阶段最适合做的工作是“搭底座”。搭底座不是马上建全套城市系统,而是把最小可复用的接入、调度、回执、仿真链路先跑通。后面的章节就直接从这个底座开始。
2. 搭建最小端云链路:让一台机器人的状态稳定上云
2.1 最小闭环要先定义清楚
如果从零开始建设 Robocity,不必直接引入复杂微服务。先花一晚上跑通一个最小闭环:机器人端定时上报状态,消息代理负责转发,后端服务订阅消息,并在内存里更新机器人实时状态。最小闭环虽然只有三层,但它会暴露连接、序列化、Topic 结构和 QoS 这四大基础问题。
建议的本地目录结构如下:
robocity-min/ broker/ mosquitto.conf robot/ robot_sim.py platform/ gateway.py README.md将该收起的目录结构保存下来后,就可以逐个文件实现。需要说明的是,下面使用的robocity-min只是演示项目的内部代号,不同团队完全可以换成自己的项目名。
2.2 为什么第一版通信优先考虑 MQTT
机器人远程控制和高频状态上报场景,第一个需要面对的问题是通信协议选型。HTTP 长轮询适合低频请求,gRPC 适合服务间强类型调用,而MQTT更适合设备侧弱网环境下大量小消息的发布订阅。
MQTT 的关键设计是 Broker 模式。机器人不直接和后端服务建立长连接,而是连接到 Broker 上发布主题消息;后端服务也连到 Broker,订阅自己关心的主题。这样做的好处是解耦:增加一台机器人时,不需要修改后端服务的网络地址,只需要让机器人连接同一个 Broker,后端订阅的主题可以按规则覆盖新增机器人。
学习环境可以只用公开的 mosquitto 镜像。
2.3 Broker 的本地配置和启动
先创建broker/mosquitto.conf,这个配置只用于本地验证:
listener 1883 allow_anonymous true persistence false log_dest stdoutallow_anonymous true在本地模拟阶段方便排查,但一旦进入生产环境必须关闭。在 Robocity 场景中,机器人数量会增长,权限必须收敛到设备级,而不是让所有设备共享同一个匿名通道。
在broker目录下执行:
docker run -d \ --name robocity-min-mqtt \ -p 1883:1883 \ -v $(pwd)/mosquitto.conf:/mosquitto/config/mosquitto.conf \ eclipse-mosquitto:2.0启动完成后,可以用下面的命令验证 Broker 是否可连接:
mosquitto_sub -t 'robocity/robot/+/state' -v -h 127.0.0.1 -p 1883如果当前机器没有安装 mosquitto 客户端,也可以安装:
sudo apt install mosquitto-clients2.4 机器人端:发布状态消息
创建robot/robot_sim.py,模拟一台机器人每隔 2 秒上报自身状态。这里不依赖真实底盘,主要用来验证消息链路。
import json import random import time from paho.mqtt import client as mqtt BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883 ROBOT_ID = "robot-demo-001" def build_state(): return { "robot_id": ROBOT_ID, "status": "idle", "position": { "x": round(random.uniform(-50, 50), 2), "y": round(random.uniform(-50, 50), 2) }, "battery": round(random.uniform(60, 100), 1), "timestamp": int(time.time() * 1000), } def main(): client_id = f"{ROBOT_ID}-pub" client = mqtt.Client(client_id=client_id, protocol=mqtt.MQTTv311) client.connect(BROKER_HOST, BROKER_PORT, keepalive=30) client.loop_start() while True: state = build_state() topic = f"robocity/robot/{ROBOT_ID}/state" payload = json.dumps(state) client.publish(topic, payload, qos=1) print(f"publish -> {topic}: {payload}") time.sleep(2) if __name__ == "__main__": main()这一段的核心不是机器人本身,而是topic的组织方式。主题使用robocity/robot/{robot_id}/state,后端订阅robocity/robot/+/state就能收到所有机器人的状态。这里的+是 MQTT 的单层通配符。
实际机器人项目中,发布频率需要根据网络带宽和后台处理能力决定。速率过高会导致 Broker 队列积压,速率过低又会让调度中心误判机器人失联。模拟阶段先使用 2 秒一次,后续再做动态调频。
2.5 后端服务:订阅状态并实时更新
创建platform/gateway.py,它连接 Broker,订阅所有机器人状态主题,并把最新状态保存到内存字典中。
import json import time from paho.mqtt import client as mqtt BROKER_HOST = "127.0.0.1" BROKER_PORT = 1883 CLIENT_NAME = "robocity-gateway" state_cache = {} def on_connect(client, userdata, flags, reason_code, properties=None): print(f"connect result: {reason_code}") client.subscribe("robocity/robot/+/state", qos=1) def on_message(client, userdata, msg): try: payload = json.loads(msg.payload.decode("utf-8")) except json.JSONDecodeError as exc: print(f"bad payload: {msg.payload}, error: {exc}") return robot_id = payload.get("robot_id") if not robot_id: return payload["last_seen"] = int(time.time()) state_cache[robot_id] = payload def main(): client = mqtt.Client(client_id=CLIENT_NAME, protocol=mqtt.MQTTv311) client.on_connect = on_connect client.on_message = on_message client.connect(BROKER_HOST, BROKER_PORT, keepalive=30) client.loop_forever() if __name__ == "__main__": main()实际生产系统中,state_cache会被数据库或 Redis 替换。不过在这个最小闭环里,内存字典足以验证链路。也可以额外暴露一个 HTTP 接口,从state_cache返回机器人状态,便于其他系统查询。
验证时先启动 Broker,再启动robot_sim.py,最后启动gateway.py。如果日志里能持续出现publish和connect result: 0,说明端云链路已经打通。此时用mosquitto_sub也能实时看到状态数据。
注意:端云链路跑通还只是第一步,后续还要验证断线重连、异常消息、消息积压和权限配置,否则只能算局域网内演示。
3. Topic 结构和指令回执:多机接入时的两个关键设计
3.1 主题规划要比代码先落地
第一版 Demo 只需要一个状态主题,但当机器人数目增加到几十台时,主题规划就变成了系统设计问题。如果每个工程师按自己习惯拼接主题,后期排查跨模块问题时,往往不知道应从哪个 Topic 开始抓包。
建议按“域/设备类型/设备 ID/消息类型”的分层方式规划,并形成下表这样的主题规范。
| Topic 表达式 | 方向 | 用途 |
|---|---|---|
robocity/robot/{robot_id}/state | 机器人 -> 平台 | 高频状态上报 |
robocity/robot/{robot_id}/event | 机器人 -> 平台 | 任务事件、异常事件 |
robocity/robot/{robot_id}/command | 平台 -> 机器人 | 控制指令下发 |
robocity/robot/{robot_id}/command_reply | 机器人 -> 平台 | 指令执行回执 |
robocity/robot/{robot_id}/log | 机器人 -> 平台 | 设备日志 |
规则需要尽早统一。如果已经运行一段时间后再改主题,则必须通知 Broker 上的所有生产者和消费者,迁移成本很高。
3.2 QoS、Retain 和遗嘱消息要分别使用
MQTT 提供 QoS 0、1、2 三个等级,很多机器人项目只使用默认 QoS 0,结果出现状态丢失后很难追查原因。
- QoS 0:最多一次,适合日志和低频事件,丢消息影响可接受。
- QoS 1:至少一次,会重复投递,适合状态上报。
- QoS 2:只有一次,通信开销较大,适合指令等不允许重复的业务。
在 Robocity 中,状态消息建议设置为 QoS 1,指令消息建议也使用 QoS 1,但业务层必须通过回执去重。Retain 消息要慎用。Retain 适合保存“最新一条已知状态”,例如机器人当前是否在线;不适合高频遥测,因为每一条遥测都保留会占用 Broker 内存,也会让新订阅者收到旧数据。
last will也比较重要。可以在机器人连接 Broker 时设置遗嘱消息,让机器人在异常断线时自动发布离线事件。有一个常见坑是只关注正常上报,而忽略异常断线,后台很容易出现“这台机器人十分钟前还在线,但实际已经被搬走”的假象。
3.3 指令下发不能只发不管
机器人控制与 Web 消息最大的区别是:控制指令会影响物理设备,如果机器人没收到命令、收到了但执行失败,后台必须能区分出来。推荐使用三段式回执:
- 后台下发指令,写入主题
robocity/robot/{robot_id}/command。 - 机器人收到后立即回一条
pending,表示指令已到达。 - 机器人执行完毕后回
success或rejected。
示例如下:
{ "request_id": "req-10001", "robot_id": "robot-demo-001", "type": "move_to", "target": {"x": 10.0, "y": 20.0}, "timeout_s": 30 }机器人端会首先返回 pending:
{ "request_id": "req-10001", "status": "pending", "message": "command received" }执行成功后再返回成功:
{ "request_id": "req-10001", "status": "success", "elapsed_ms": 4200 }如果指令无法执行,返回 rejected:
{ "request_id": "req-10001", "status": "rejected", "reason": "battery_too_low" }后台需要维护一个“指令超时表”。如果在timeout_s后仍未收到 success,则不能盲目重发同一带物理动作的指令,而应先查询机器人当前状态,再决定是重试还是切换到人工介入。
3.4 多机接入阶段最容易踩的三个坑
第一坑是把状态主题和指令主题混在一个主题里。比如用robocity/robot/robot-001/all既发状态又收指令,这会导致权限无法精细控制,也难以进行快速检索。
第二坑是相同client_id被多个客户端使用。MQTT Broker 一般会踢掉旧连接,让同一client_id的新连接生效。如果调试时开了多个订阅程序,又没有给每个客户端独立命名,就会出现“订阅端莫名其妙掉线”的现象。
第三坑是回执缺失却仍然更新任务状态。常见错误写法是平台把指令 publish 出去之后,立刻把任务标为“执行中”,然后不再关心机器人是否真的收到。正确做法是等待机器人返回 pending 后再标记为已派发,等待 success 后再标记为已完成。
4. 先让仿真跑在真实场地前面:Robocity 平台的验证闭环
4.1 仿真不是“可选环节”,而是调度系统的回归工具
城市级机器人平台最怕的不是功能实现不了,而是版本更新后,原有场景出现回归。今天修改调度规则后,单台机器人可能没有问题,但 50 台机器人同时执行任务时,可能会出现死锁或等待超时。
这时需要仿真正式参与开发流程,代替物理设备提前验证。Robocity 平台应该把所有业务组件都设计成“不感知真机还是仿真”的形态。真实机器人和仿真器都通过同一套接口上报状态、接收指令并返回回执。这样,算法版本、调度规则、任务状态机可以在仿真环境中批量回归。
建议先抽象出机器人的最小接口:
class RobotAdapter: def get_state(self) -> dict: raise NotImplementedError def handle_command(self, command: dict) -> dict: raise NotImplementedError真实底盘和仿真器分别实现这个接口。调度中心只依赖接口,不关心底层是通过 ROS 控制真实底盘,还是通过模拟器移动虚拟坐标。设计这一点对于机器人数增长后的测试至关重要。
4.2 用 Scenario Runner 批量模拟多机器人
在 Robocity 场景中,不能只做单机仿真,而是要做多机并发仿真。下面给出一个简化版 Scenario Runner,它不依赖真实机器人,只模拟多台机器人上报状态和执行移动指令的耗时,用于验证调度的基本逻辑。
import time from dataclasses import dataclass, field @dataclass class SimRobot: robot_id: str status: str = "idle" battery: float = 100.0 position: tuple = (0.0, 0.0) class ScenarioRunner: def __init__(self, robot_count: int, rounds: int): self.robots = { f"sim-{i:03d}": SimRobot(robot_id=f"sim-{i:03d}") for i in range(robot_count) } self.rounds = rounds def run(self): success = 0 for step in range(self.rounds): for robot in self.robots.values(): robot.battery -= 0.1 if robot.battery <= 0: robot.status = "charging" else: robot.status = "idle" success += 1 time.sleep(0.02) return success if __name__ == "__main__": runner = ScenarioRunner(robot_count=50, rounds=100) result = runner.run() print(f"simulation finished, success events: {result}")真实项目中会把这种“空转”仿真替换成真实地图上的移动仿真,核心思路是一样的:在可控环境里用大量机器人验证业务系统稳定性。运行命令可以做成:
python scenario_runner.py --robot-count 50 --rounds 2004.3 仿真指标必须落到调度系统上
仿真运行完以后,不能只打印一句simulation finished,还需要把指标和真实场景进行对照。
| 指标 | 含义 | 理想方向 |
|---|---|---|
| 任务成功率 | 成功回执数与总任务数之比 | 高 |
| 指令平均时延 | 从指令下发到 pending 回执的时间 | 低 |
| 超时任务比例 | 超过 timeout_s 的任务比例 | 低 |
| 重试次数 | 同一 task 被调度几次 | 越低越好 |
| 机器人闲置率 | 有可用机器人但没有派单的比例 | 需平衡 |
这些指标应该落库或输出成结构化 JSON,供版本对比。每改动一次调度规则,就重放同一批场景,否则很难判断性能下降是代码回归还是场景差异导致。
4.4 数据回放是 Robocity 的另一条护城河
平台不能只保留最新状态,还需要保留历史消息。出现问题时,最直接的做法是根据时间戳回放。最简单的回放方式是定期把 MQTT 原始消息写入对象存储或消息队列,回放时按照原始时间顺序重新发布。
所以,从第一天起就要给每一条消息打上完整时间戳,并保留request_id或robot_id作为关联索引。没有这些信息,后续几乎无法定位“当时到底机器人收到了什么指令”。
5. 从“连接机器人”升级到“调度服务”:任务编排与调度规则
5.1 城市级任务往往由多个原子动作组成
单机场景通常只完成一个任务,比如“从 A 点走到 B 点”。Robocity 场景中,后台派给机器人的任务往往是复合任务。比如送物任务可能被拆成“移动到取件口、取货、移动到配送点、放置到货柜”。
这种复合任务不能只是简单调用一次导航接口,而要用任务定义来表达。仿照下面这样先定义 JSON 任务体:
{ "job_id": "job-2026-0001", "type": "delivery", "steps": [ {"action": "goto", "target": "shelf-A1", "timeout_s": 180}, {"action": "pickup", "target": "box-32"}, {"action": "goto", "target": "gate-B2", "timeout_s": 300}, {"action": "dropoff", "target": "delivery_dock"} ] }调度平台把任务解析为顺序执行的步骤,每步对应一组指令。步骤之间是否需要用户确认,或者是否需要机器人到指定充电位,要根据业务规则单独配置。
5.2 任务状态机不能省略
一个任务在 Robocity 平台中至少经历以下状态:
pending:任务已进入队列,尚未分配机器人。scheduled:已经分配到目标机器人,等待机器人确认。executing:机器人已收到首个步骤指令,开始执行。success:所有步骤执行完毕并收到成功回执。exception:出现机器人断线、任务超时、指令被拒绝等异常。
后端代码不能通过if status == "scheduled" and ...到处散落判断,而应该使用统一状态机。状态机的好处是能识别非法跳转。比如一个任务从pending直接变成success,必然是代码 bug,因为中间缺少了执行环节。
5.3 选择机器人时不能只看电量
调度系统在任务开始时需要从多台机器人中挑选最适合的一台。最简单的策略是“状态等于 idle,且电量充足”,但当调度规模扩大后,还要考虑机器人当前位置、所属区域、维护状态、负载能力和网络质量。
下面提供一个朴素候选过滤代码,便于说明筛选思路:
def select_robot(robots, task): candidates = [] for robot in robots: if robot.state.get("status") != "idle": continue if robot.state.get("battery", 0) < 30: continue if robot.busy is True: continue # 优先选择离起点更近的机器人 candidates.append( (robot.distance_to(task["start_point"]), robot.robot_id) ) if not candidates: return None candidates.sort() return candidates[0][1]实际生产项目不会允许业务代码直接控制机器人移动,而是通过调度服务向机器人下发command,再由机器人自主执行安全避障。调度平台更关注“把任务分配给谁”,底层运动控制仍然保留在机器人端或边缘控制器中。
5.4 调度规则改动前必须先做仿真回归
调度规则直接影响机器人利用率、任务时延和设备安全。任何规则调优,都应该先进入仿真环境。比如把电量阈值从 30% 改成 40%,会在仿真环境里看到任务拒绝率上升、充电次数增加,这些结果必须在正式发布前由算法或业务同学确认。
另一个容易被忽略的问题是死锁。两辆机器人需要进入同一个狭窄区域时,如果没有区域锁机制,就可能互相等待。调度系统需要支持“区域占用”的判断,在任务分配前检查目标区域状态,避免多机同时进入同一物理空间。
6. 生产环境补短板:设备权限、网络隔离与可观测性
6.1 Broker 不能允许匿名访问
学习阶段的 mosquitto 配置为方便调试开启了匿名访问,但 Robocity 一旦涉及任务派发和真实设备控制,必须关闭匿名通道。
更安全的做法是:
- 为每台机器人分配独立账号。
- 为平台服务和 Web 后台创建不同权限账号。
- 关闭不必要的主题写权限。
- 有条件时使用设备证书或动态 Token。
对于 Broker 侧的访问控制,可以借助 ACL 文件。下面是一个思路示例:
user control-center topic read robocity/robot/+/state topic read robocity/robot/+/event topic write robocity/robot/+/command_reply topic readwrite robocity/robot/+/command user robot-001 topic write robocity/robot/robot-001/state topic write robocity/robot/robot-001/event topic read robocity/robot/robot-001/command topic write robocity/robot/robot-001/command_reply由于不同 Broker 版本的 ACL 语法有差异,落地前必须对照当前版本调整。原则是:让后端控制中心只能操作业务主题,不能订阅机器人的内部日志;让机器人只能访问自己的主题,不能读取其他机器人位置。
6.2 生产环境的网络分区
Robocity 至少需要划分三个网络区域:
- 机器人本体网络:机器人底盘、传感器集群、控制器之间通信。
- 边缘接入网络:机器人和边缘网关之间的通信。
- 中心云网络:边缘网关与中心调度平台之间的通信。
机器人不应直接暴露在公网。当需要远程访问时,建议通过边缘网关代理或消息代理转发,而不是让设备端口直接映射到公网。边界设备需要对未知来源的连接做访问限制,防止攻击者通过一把抓取工具进入机器人控制链路。
6.3 日志、指标和链路追踪要统一格式
在 Robocity 这种设备端与云端交织的系统里,一天可能产生几十万条消息。要能定位问题,每个模块需要输出结构化日志,至少包含时间、模块名、设备 ID、请求 ID 和事件类型。例如:
{ "time": "2026-01-01T10:00:00.123Z", "module": "scheduler", "robot_id": "robot-1001", "request_id": "req-8877", "event": "dispatch_timeout", "detail": "no command_reply in 30s" }指标至少包括:Broker 连接数、消息吞吐量、状态更新延迟、任务成功率、指令超时率、机器人掉线率。链路追踪要能把一个任务从进入队列到每个步骤执行回执串联起来,便于在页面看到任务到底卡在哪一步。
6.4 版本发布时要能回滚
调度规则、地图、模型权重和机器人固件都不能用“覆盖式更新”简单处理。建议发布前保留上一版本,并设计回滚触发条件。比如新调度策略在 10 分钟内任务成功率下降 5%,系统需要自动切换回旧策略。
真实场地的回滚不仅仅是代码回滚,还包括地图版本回滚和操作策略回滚。地图数据若更新错误,机器人导航可能出现偏航,因此地图文件也应该包含版本号,并能在调度中心一键切换可用版本。
7. 从故障现象反查 Robocity 链路:一份可复用的排查清单
7.1 常见故障现象与可能原因
先看现象,再查根因,是定位 Robocity 问题最有效的方式。
| 故障现象 | 常见原因 | 优先检查项 |
|---|---|---|
| 页面显示机器人离线 | Broker 连接断开、心跳上报停止 | 机器人端日志、Broker 连接数 |
| 机器人在线但任务不派发 | 状态中status不是idle | 状态缓存中最新机器人状态 |
| 指令已下发但无回执 | 机器人端订阅失败或 QoS 配置错误 | 是否启动订阅程序、Topic 拼写 |
| 任务一直停留在 executing | 成功回执丢失或回执消息被过滤 | 回执主题权限 |
| 多台机器人同时重复执行任务 | 未正确处理回执或调度没有加锁 | 调度服务日志、任务状态机 |
上面的现象在真实项目中经常交替出现。排查时不要一次性打开几十个文件看代码,先按下面顺序收窄范围。
7.2 按照“端、路、存、调”四段排查
把 Robocity 消息链路拆成“端、路、存、调”四段:
- 端:机器人到底有没有上报状态?有没有收到指令?
- 路:Broker 上数据有没有转发?订阅权限是否正确?
- 存:后端缓存或数据库里到底保存了什么?
- 调:调度引擎为什么没有做出预期动作?
第一步是检查机器人端日志。如果根本没有新的上报日志,问题大概率在端侧或网络连接,没有必要先去改调度规则。
第二步是使用 Broker 端订阅命令直接观察链路:
mosquitto_sub -h 127.0.0.1 -p 1883 -t 'robocity/robot/+/state' -t 'robocity/robot/+/command' -t 'robocity/robot/+/command_reply' -v如果能看到状态和回执,说明链路已通,问题在业务层。如果只能看到机器人发布,但看不到平台下发,就需要检查后端订阅和权限配置。
第三步是确认后端缓存是否更新。如果订阅程序收到消息,但任务模块依然读取旧状态,则要检查内存缓存是否在共享层面丢失,或者是否存在多副本不一致。
第四步是查看调度日志中的关键事件。常见的调度错误日志关键字包括no idle robot、dispatch timeout、command rejected、job exception。
7.3 排错还要注意日志时间和对齐
Robocity 系统中,机器人时钟可能和云端时钟不同步。排查时必须统一使用毫秒级时间戳,并在日记模块里确认时钟来源。如果机器人本地时间和服务端时间相差几秒,回执超时判断就会失真。
之前也提到,一条指令从一个任务步骤产生开始,必须携带同一request_id或者job_id。排错时,只需要按照request_id去检索机器人端日志、Broker 转发记录、云端入库记录和调度日志,就能完整还原一次任务的命运。如果某个环节缺少request_id,多数情况下会无功而返。
7.4 把这次的排查经验沉淀成检查清单
Robocity 不是一个一次性的项目,每次故障处理形成的经验都应沉淀为自动化检查。下面的发布前检查清单可以移植到自己的项目中:
- 所有机器人使用独立账号或证书,关闭匿名访问。
- Topic 名称已经经过版本评审,并能区分状态、事件、指令和回执。
- 状态消息、指令消息、回执消息都定义了 QoS。
- 指令回执包含 pending/success/rejected 三种结果。
- 任务状态机能够识别非法跳转。
- 调度规则改动已通过仿真场景回归。
- 日志中带有 robot_id 和 request_id。
- 地图、模型和调度策略有版本和回滚方案。
- 针对“机器人离线和指令超时”有自动告警。
对开发团队来说,Robocity 真正值得先投入的不是让一台机器人变得更聪明,而是先把消息链路、回执机制、仿真回归和权限边界这些底座搭稳。2026 年大概率还会出现新的传感器、新的模型和新的机器人形态,但只要平台能在复杂设备环境中保持可靠,后续新增设备本身就会变成一件相对轻松的事情。