休斯顿,我们遇到了一个问题
最近和前端同学聊得比较多,聊着聊着发现前端回报了一个诡异的错误,服务器端发给客户端的 JSON 错了,解不开。前端同学 bs 了一下服务器用的 JSON 库,居然连编码都会出错。我检查了所用的库,前后端的确用的不是同一个 json.lua,于是跑了一次简单的压测。当然,没有发现问题。过了两天,又遇到同一个问题了,这次我留心了一下错误的详细内容:
coco/game.lua:86: coco/lib/json.lua:525: coco/lib/json.lua:240: expected colon at char 119 of: [{"res_amount":5,"id":1,"status":2,"res_type":1},{"res_amount":50000,"id":2,"status":0,"res_type":2},{"res_amount2:5,"id":3,"status":2,"res_type":1},{"res_amount":50000,"id":4,"status":0,"res_type":3},{"res_amount":7,"id":5,"status":0,"res_type":1},{"res_amount":50000,"id":6,"status":0,"res_type":5},{"res_amount":8,"id":7,"status":0,"res_type":1}]
可以看到,具体错误:
{"res_amount2:5,"id":3,"status":2,"res_type":1}
本来是引号的地方,变成了 2。比较一下两者的二进制表示: |0011 0010|2 |0010 0010|”
两者只差一个比特!再找到上一次的错误,是一个 , 变成了<: |0010 1100|, |0011 1100|<
看来,我们遇到了传说中的比特反转!相关的资料可以参考 这篇文章 。
一时好奇心起来,就写了个爬虫,爬了最近十天的日志记录(没有权限查数据库 Orz),分析了约 80 万条日志,总共扫出了 6 例类似的报错。发生的机型以国产机居多,以下是点名时间(啪啪啪): |SM-G3502 |Coolpad 8705 |Coolpad 8720L |HUAWEI G521-L076 |ASUS_T00F |vivo X3t
品牌从三星到华为,再到接地气的 coolpad 都有。
相信实际上发生的比特反转几率会比这个还要多,因为发生在 json 字符串中间,或者 json 数值中间的,都不会被发现。另外,二进制打包是用 protobuf 做的,pb 包损坏的报错其实也不少,只是这个就没法确认是 1 比特坏了还是多个比特坏了。据 参考资料 说,4G 内存的 PC 机,每小时 3bit 错误到每个月 3bit 错误都有,而移动设备的数量比 PC 要多得多,所以出现这种情况一点都不奇怪。
及后,我分析了一下我们的协议收发过程,当时实现的时候没有加上验证码。如果加上了,可以补上一个包重发机制,遇到错误的包要求服务器重发。或者添加某些校验码,类似服务器内存使用的 ECC,可以让只错了 1bit 的包恢复回来。第一次发现 TCP 也会不可靠,哈哈