记 Rokid 两周以及一次 ShadowNode MQTT 的 bug
发布于 2 个月前 作者 Txiaozhe 703 次浏览 来自 分享

2019年6月某天,入职Rokid第2周,逐渐适应了新的办公环境和新的做事方式,开始接触IOT和端开发的概念,技术上慢慢地从服务端过渡到端开发,刚开始还是有些不适应,先讲一下个人感受吧!

以前做过大规模的分布式系统,也做过小型的Web API,总结下来,做服务端开发要考虑更多的是Web接口、请求负载、网络性能以及如何更好地和客户端交互,大多数情况下不需要考虑太多机器的性能问题,因为一般情况下机器性能都是过剩和冗余的,对于企业来说,能花钱加机器解决的问题也一定不是问题,而且现在云服务盛行,大量的工具、平台都被打包成云服务出售,只要花上一点小钱钱就能获得很好的基础设施支撑,开发起来不要太爽,像把服务器性能榨干这种事情基本是被抛在脑后的。但端开发就是另外一种情景了,和服务端性能压力来自外部请求不同,端上的性能问题来自设备自身,当下呈现在端开发工程师面前的主要是嵌入式开发板、智能家居设备和可穿戴式设备等,CPU和内存资源少得可怜,同时也不可能像服务端那样通过增加设备来横向扩展,这就需要开发者去耐心地打磨底层的代码,想为所欲为地写代码秀操作?不存在的,碰到性能瓶颈?C语言伺候。

开始的这两周,主要以熟悉业务为主,开始还是有点吃力的,难点在于思维方式的转化,幸运的是大佬们都很耐心和细心,指导起来也不遗余力。熟悉业务以线上的各种bug为切入点,这两天就碰到一个关于MQTT协议的问题,经过两天的修复和审核,总算是将补丁打入主分支。

MQTT(消息队列遥测传输协议),是一种基于发布/订阅模式的轻量级应用层通信协议,基于TCP协议构建,因为其低开销、低带宽占用的特点,使其在物联网(IOT)方面有着较广泛的应用。Rokid产品线基于YodaOS + ShadowNode 构建 IOT 应用,并使用 ShadowNode 内置的 MQTT 作为设备与移动端之间的通信协议。在某一时间点,用户爆出大量设备不在线的问题,初步分析是 MQTT 的问题,拿了一段 MQTT 订阅和发布的测试代码在本地运行,发现用 node 命令运行是没问题的,用 ShadowNode 运行就会出现断开连接的问题,确实是ShadowNode 本身的问题,再细致分析,发现客户端在 SUBSCRIBE 的时候会导致断开连接,于是深入MQTT源码,打印出 SUBSCRIBE 时发送的报文,再将其在 node 环境中运行打印正常的报文进行对比:

82 2d 00 00 00 28 2f 64 65 76 69 63 65 2f 43 4d 43 43 2d 33 30 36 37 31 2d... # 错误报文
82 2d 8d 05 00 28 2f 64 65 76 69 63 65 2f 43 4d 43 43 2d 33 30 36 37 31 2d... # 正确报文

对比正确的和错误的报文,发现第三和第四个字节是不一样的,看来是报文组装出问题了,为了验证这个猜想,我将正确的报文内容转成二进制通过ShadowNode发送,果然正确订阅,看来确实是协议的问题。鉴于本人也是第一次接触MQTT协议,没办法,只能去看MQTT协议文档了,本次修复查阅的是 3.1.1 版本的文档。

简单阐述一下MQTT协议,为了节省性能开支,其报文设计得相当紧凑环保。MQTT的报文分为固定报头、可变报头和有效载荷三部分,固定报头是必须存在的,用于描述报文类型、等级等信息,可变报头不一定存在,这取决于报文的类型,有效载荷存放的是通信内容。如上所示,前两个字节是固定报头,第一个字节(82)前四位表示类型,这里8表示SUBSCRIBE消息,后四位表示指定控制报文类型的标志位,目前只用到0010,也就是2。第二个字节表示消息的剩余长度,也就是可变报头+负载的长度总和。第三第四个字节为可变报头部分,包含一个用于确认消息传递状态的报文标识符(PacketId),该标识符由报文类型决定存在与否,当发布PUBLISH消息且QoS > 0时是必须存在的,且发送 SUBCRIBE 消息时也是必须存在的,问题就出在这里,在 ShadowNode 的 MQTT 协议实现中会传入一个从0开始递增的PacketId,首次 SUBCRIBE 时该 PacketId 为0,这就导致底层处理时认为该位为空而不写入该段报文,也就是为什么上面看到的错误报文的第三和第四字节为00 00,正常情况下应该写入一个两字节的非零数字作为PacketId。至于开始时为什么不出错,也许是应用该MQTT实现的一端开始时并没有真正将这个PacketId 用来校验消息状态,而后期迭代的时候开发人员良心发现突然又校验了,从而导致报文错误。Bug 原因已找出,是时候修复一下了,由于 ShadowNode 中 MQTT 的实现还没有很完善,后期还需要做更多的适配,而业务线又出现 Bug 需要紧急修复,因此目前只做了应急措施,将上述 PacketId 的递增初始值设置为1,进而解决了该问题,真是一个数字导致的血案~ 为了适应协议文档中对QoS的处理,本次补丁还对 QoS 进行了初步的校验,补丁地址,这是情急之下做的紧急修复,如有问题欢迎在 ISSUE 中提出 :)

两周下来,切实感受到 Rokid 确实是一片丛林,挣扎并有趣~ 可期!

8 回复

竟然感觉像在逛 Rokid 内部的 ATA。。。

@hyj1991 哈哈是吗?

特意看了下issule ,奇怪yorkie是老外?

@Txiaozhe 基本上有很多是借鉴 MQTT 的,那块关于MQTTPacket的和阿里的mqtt 一样的,好像很多都是同样的,这个框架的意义在哪里

请问一下你们mqtt 的broker用的什么实现的?也是用node做的吗(mosca 或者aedes)?

@jxycbjhc 这里说的是ShadowNode 的 mqtt模块,并没有一个单独的mqtt框架,我不是很明白你说的“框架”是指什么?如果是指ShadowNode,那我想我们是想做一个可用于嵌入式设备的nodejs运行时并能够利用现有的生态优势降低IoT、嵌入式开发的门槛吧!?

@MichealDean 我们没有自己做实现,而是基于eclipse的mqtt库做了一层封装~

回到顶部