Hugging Face 的机器鸭爆火后,我扒了它的代码:7 个 Rust 守护进程,比多数后端架构还讲究

一只 25 厘米高、800 克重的机器鸭,8 天在 GitHub 拿了 6994 个 star,399 美元预售被抢到断货。大多数文章都在写它"萌"——能走、能滑、跌倒自己爬起来、还会嘎嘎叫。
但真正值钱的不是鸭子,是这只鸭子体内的代码。我把它 7 个 Rust 守护进程的架构文档从头读了一遍,发现这套嵌入式系统设计,比市面上多数后端服务还讲究。而且它里面的每个决定——服务怎么拆、进程怎么通信、更新怎么安全落地——都能原样抄回你的业务系统。
它不是一个"程序",是 7 个守护进程
Microduck 跑在一块 Rockchip RK3566 上(25 美元级别的国产 SoC,四核 A55),整个系统是 Rust 写的、无框架、单 workspace,拆成 7 个守护进程:
robotd:唯一能碰机器人的进程。50Hz 控制循环独占 15 个伺服电机 + IMU 的串行总线,客户端只能发"意图"(走快点/看那边/站起来),由它内部的安全层决定什么真正可执行updaterd:更新引擎。验证签名、切换版本、健康门控回滚configd:wifi、身份、配对 PINbtd:蓝牙传输层(手机 App 入口)padd:游戏手柄读取mediad:摄像头 + WebRTC 推流(最重的服务)tofd:头部 ToF 深度传感器(8×8 深度矩阵,只发布不读取)
关键原则一句话:每个进程只拥有自己那份状态,单写者,其他人只能读或订阅。
为什么拆成 7 个而不是 1 个
这里藏着最值得抄的设计。作者在文档里写得很直白:“拆 3 个进程是为了让第 1 个可以坏掉,板子仍然可用。”
具体规则:
① 三个进程必须能在 robotd 死掉后幸存——configd、updaterd、btd 不依赖 robotd 的 systemd、不依赖 ML 运行时、不依赖媒体栈。原因很朴素:一台控制循环起不来的机器人,恰恰是最需要被人重新配置、更新、回滚的机器人。这仨就是"恢复路径"。
② 媒体崩溃不能带崩电机控制——mediad(最重)和 robotd 必须分离。同理 tofd 单独成进程:给 VL53L5/8CX 上传固件要走 I²C、要好几秒、还和音频编解码器共享总线,这种重试循环不该住在控制电机那个进程里。
③ 传输层不拥有任何状态——btd/padd/mediad 是纯传输适配器,全部可替换而不影响机器人行为。它们每天都被真实使用,所以 App 要用的 API 不会悄悄腐烂。
这套逻辑放到后端就是:你的支付服务挂了,日志服务和配置中心必须还活着——因为出问题的那一刻恰恰是最需要它们的时候。很多人把可观测性组件和业务组件部署在一起,等于在"最需要诊断的时刻"把诊断工具也带崩了。
控制面和数据面分离,是教科书级示范
这个 repo 里把流量分成两类的做法,直接可以抄:
| 控制面 | 数据面 | |
|---|---|---|
| 内容 | 命令、配置、状态、感知事件 | 视频/音频帧 |
| 大小/速率 | 几十字节,≤100Hz | 640×480 RGB@30fps ≈ 27MB/s |
| 传输 | Unix socket JSON-RPC | 绝不过 socket |
控制面用 JSON-RPC 2.0 + NDJSON(一行一个对象),走 Unix socket。为什么不用消息总线?文档原话:“N=4 的时候没有引入 broker 的理由——总线是另一个可能挂掉的组件,而且它违背了’恢复路径必须独立’这条不变式。”
数据面(视频流)从不跨 socket——27MB/s 的流量如果走 JSON-RPC 会让控制循环直接卡死。这是嵌入式系统里最经典的一课:控制指令和数据流必须走不同通道。放后端就是:业务 API 和日志/指标管道必须分离,否则一次日志洪水就能把你的业务请求拖死。
更新系统:签名验证 + 健康门控 + 自动回滚
这是全 repo 最精彩的部分,也是 updaterd 被第一个构建的原因——作者明确说"先把更新系统的风险前置,在还没有客户、没什么可破坏的时候犯错"。
更新流程:
|
|
细节:
- 发布是"整个目录原子替换",不是打补丁——每个版本一个独立目录,
current是指向活版本的符号链接 - 健康门控是硬性的:
robotctl health一次回答硬件(robotd)+ 软件(updaterd)两方面问题,非零退出码可做脚本门控——“这台机器人哪里坏了"不能先分硬件软件,因为回滚一小时前的机器人看起来和电机没供电一模一样 - 回滚失败还有保险:boot counter 兜底——启动计数超限自动回滚,防"回滚后仍崩溃循环”
这套"签名 + 健康门控 + 自动回滚"放到后端就是:发布系统必须能自己判断"这次发布是不是搞砸了"并自己退回。很多团队发布靠人盯着 dashboard,而 Microduck 这种"25 美元芯片上的玩具"都在做自动回滚——因为它知道发布后没人会守在鸭子旁边。
50Hz 控制循环的硬实时约束
robotd 的控制循环有几条铁律,值得每个做实时系统的人抄:
- 控制循环永不阻塞等待其他服务——所有跨服务读取都是"last-value-wins"缓存,绝不发同步 RPC
robotd是安全的唯一权威——没有客户端能绕过跌倒检测、关节/温度限制、安全姿态逻辑。客户端发意图,robotd决定能否执行- 里程计不是服务,是循环里的一个 struct——因为它的输入恰好就是循环刚读的那批采样,做成服务反而要跨进程拷贝
这三条翻译成后端:你的核心路径上不能有同步跨服务调用;安全决策必须集中在一个权威点,不能被外部绕过;属于你的数据不要为了"架构漂亮"拆出去。
策略是怎么训练的:MuJoCo + PPO 的 sim2real
机器人的"大脑"(走路/站立策略)不在主仓,在隔壁的 microduck_rl 仓库:
- MuJoCo 物理仿真 + PPO 强化学习
- domain randomisation(域随机化):训练时随机化物理参数(摩擦、质量、电机延迟),让策略在真机上也能工作——这就是 sim2real 的核心 trick
- 训练完导出 ONNX,主仓加载推理
流程是:仿真训练 → ONNX 导出 → 真机部署。这个闭环以前是机器人实验室的专属,现在 Apache-2.0 开源了,普通开发者也能碰。这可能是 Microduck 最深远的意义:它把 RL 从"论文里的概念"变成了"25 美元硬件上能跑的代码"。
明天能试什么
如果你不打算买鸭子(虽然 399 美元真的很想剁手),这套设计至少有三件事可以直接抄:
- 恢复路径隔离:下次拆服务时问自己——“我的核心服务挂了,诊断工具还活着吗?“如果答案是否,参考它的不变式:恢复路径不依赖核心服务、IPC 超时限定、依赖面最小
- 发布自动回滚:给发布流程加健康门控(一个非零退出码的健康检查)+“失败自动回退”,先于任何新功能落地
- 控制面/数据面分离:检查你的系统里有没有"大流量和小指令走同一条管道"的地方,有就拆开
一句话:Microduck 火是因为可爱,但它值 6994 个 star 是因为——25 厘米的机器人身上,跑着一套可以当教材的分布式系统设计。它证明了 Rust + 无框架 + 认真拆服务,在 25 美元的芯片上也能跑出教科书级的可靠性。
搬砖程序员带你飞,专注 Golang / AI / 后端。每天一篇,讲清楚一个技术真相。