Go 进程外硬件桥接:何时该把厂商 SDK 拆出主进程¶
Go 写的酒店 PMS 直控外设时,迟早遇到一个问题:厂商只提供 C/C++ SDK(.dll/.so),没有协议文档。把 CGO 编进主进程看似最短路径,实际会付出一串长期代价。我们在身份证读卡器这个设备上做了"进程外桥接"的实践,本文记录决策依据与落地形态。
CGO 直连的四个长期代价¶
- 交叉编译断裂:CGO 启用后
GOOS=linux GOARCH=arm64的静态编译基本告别,多平台交付直接复杂化; - 崩溃域扩大:三方 SDK 的段错误会带崩整个主进程——账务引擎、夜审、门禁全陪葬;
- 运行时锁与调度:CGO 调用会占用 Go 调度模型里的 M,高并发下出现难解释的延迟毛刺;
- 内存与句柄托管:三方库的资源生命周期游离在 Go GC 之外,泄漏只能靠进程重启兜底。
桥接形态¶
把厂商 SDK 放进一个独立的小进程(我们叫 bridge),用 WebSocket 与主进程通信:
hotel-server (Go) ──WebSocket──> hotel-bridge (C#,宿厂商 SDK) ──> 读卡器/发卡器/打印机
主进程侧只保留一个薄适配器:Read/EncodeCard/PrintReceipt 三个方法,把业务语义翻译成 JSON 指令。bridge 断线时适配器返回"前台电脑离线"类友好错误,并自动进入待办重试队列——设备故障被降级为业务可感知、可恢复的状态,而不是进程异常。
什么时候值得拆¶
拆分的成本是真实存在的:多一个部署单元、多一层通信协议、多一个需要版本管理的组件。我们的判据是三条里满足两条就拆:
- 厂商只给 C/C++ 资产(CGO 不可避免);
- SDK 稳定性存疑(无文档、闭源、崩溃史);
- 设备故障不能影响核心账务可用性。
身份证读卡器三条全中,立即拆;发卡器厂商有纯协议文档,用 Go 原生写驱动 + 模拟器,不拆。
通信协议的三个设计点¶
- 指令-应答配对:每条指令带超时与请求标识,bridge 串行处理设备(硬件本来就是串行的),主进程侧不并发压设备;
- 状态与指令分离:bridge 周期上报设备就绪状态(读卡器就绪/打印机缺纸),主进程在发指令前先看状态,把"必然失败"的调用提前拦截成友好提示;
- 操作留痕在主进程:bridge 只管硬件,操作日志、审计、待办重试全部留在主进程侧——设备历史是业务数据,不该散在桥接进程里。
降级与重试¶
设备链路的本质是"会坏的"。离线时业务不中断:入住照常(卡稍后补发),失败动作进待办,bridge 恢复后一键补执行。把"硬件暂时不可用"设计成正常业务状态,而不是异常——这是桥接方案在体验上最大的回报。
小结¶
进程外桥接不是对 CGO 的洁癖,而是把厂商 SDK 的不确定性关进一个可独立崩溃、独立重启、独立升级的小盒子里。判据清晰时,这个架构决定几乎不需要犹豫。