最近在协助团队完成 ES 数据的切换(业务数据迁移),过程中遇到一个比较好玩的 BUG ,和大家分享并作为经验记录。
01 问题发现过程
通过前期的方案设计和比较,我们决定通过 elasticdump 工具来做 ES 的数据迁移,这个也是比较普遍的迁移方案,于是就动手实施了,过程中也没遇到什么问题。在最后的数据验证环节,发现有一个 ID 对应不上了,如下图所示,通过对比工具,发现一个长度较大的 ID 发生了偏移,其他的数据都没有问题。这是为什么呢?一头雾水。文章源自玩技e族-https://www.playezu.com/181445.html
文章源自玩技e族-https://www.playezu.com/181445.html根据二分法的排错思路,我们需要先确认是导出数据的问题,还是导入数据的问题。查看导出过程的中间文件,发现在导出的时候就出现了错误。于是怀疑是 elasticdump 导出功能的问题。因为这个出错的字段,主要的特征就是长度比较长(18 位),于是怀疑是精度的问题。就去查了下 elasticdump 的源码,一番查找后,果然发现有人遇到过同样的问题,并已经修复了这个 BUG,并给出了解决方案和一些猜测的原因。于是这个问题就得到了解决。在 elasticdump 的导出命令中,加上--support-big-int 参数,就可以了。文章源自玩技e族-https://www.playezu.com/181445.html
文章源自玩技e族-https://www.playezu.com/181445.html文章源自玩技e族-https://www.playezu.com/181445.html好像也很简单嘛,不是么。其实排错的过程也走过很多弯路,只是现在回顾起来看着比较轻松而以。文章源自玩技e族-https://www.playezu.com/181445.html
02 问题的根因是什么
只解决问题并不是我的风格,总得看看让我绕这么大圈才解决的问题根因是什么嘛。于是查了相关资料(结合上面 GIT 上的对话),可以确认,是因为 elasticdump 中有部分功能是用 JS 写的,而 Js 遵循 IEEE754 规范,采用双精度存储,占用 64 位,从左到右的安排位第一问表示符号位,11 位表示指数,52 位来表示尾数,因此 Js 中能精确表示的最大整数是 253(十进制 为 9007199254740992),那么大于这个数(本文中数值长度 18 位)就可能会丢失精度,因为二进制只有 0 和 1,数值太大,于是就出现了精度丢失的问题。可以在 Chrome Console 里面试了一下,果然是这样,(不是超过了能表示的最大值,而是超过了能精确表示的最大值),和 elasticdump 导出的数据变化基本类似。文章源自玩技e族-https://www.playezu.com/181445.html
文章源自玩技e族-https://www.playezu.com/181445.html再往深了想,为什么用 double 类型会出现这个问题,其他的数据类型是否会有同样的问题呢?这就涉及了数据精度的问题,在这里篇幅有限,就不再展开,有兴趣的同学可以自己去查看相关资料,本质上还是十进制小数与二进制小数相互转换产生的误差。文章源自玩技e族-https://www.playezu.com/181445.html
03 类似的问题有哪些
因为这个问题比较好玩,就又找了一些资料看了下,发现还有两个精度有关的 BUG,还蛮好玩的。文章源自玩技e族-https://www.playezu.com/181445.html
千年虫问题:这个问题相信很多人 IT 人都听说过,简单来说,就是由于前期计算机的存储资源较为昂,在表达时间时,为了节约空间,有位科学家提出了一个方案,把1960年8月11日,简写成 600811。但这样会有一个问题,就是当时被缩写掉的是 19XX 年中 19,如果时间来到 2000 年,程序就无法准确表达时间。比如:2000年1月1日,简写成六位数是 000101。计算机就会怀疑人生,怎么时间倒流了呢?然后就会导致计算机系统发生紊乱。当时大家都觉得自己的程序不会运行到 2000 年,所以就没太放在心上,而大多数后来人习惯了这种记录方式,就忘记了这回事,结果引发了千禧年的大 BUG,造就了多少程序员的不眠之夜。
2038 年问题:现在很多时候,我们在处理时间问题时,都喜欢用时间戳来记录,因为简单方便,不需要考虑时区问题(时区问题很让人头疼的,一不小出就容易出错)。但是这里面会有一个小 BUG 哟。什么是时间戳呢?简单来说就是:以1970年1月1日0 时 0 分 0 秒为起点,然后通过计算秒数来算出当前时间。比如:2021年5月7日15:00:00,换算一下就是 1620370800 秒。但是由于 32 位操作系统所能计算的秒数有限,到2038年1月19日3:14:07,就会达到极限。二进制:01111111 11111111 11111111 11111111,其后一秒,二进制数字会变为 10000000 00000000 00000000 00000000,发生溢出错误,造成系统将时间误解为1901年12月13日20 时 45 分 52 秒,然后系统就会发生各类错误,是不是和上面的千年虫一样?理论上到了 2038 年,人们应该淘汰掉了 32 位操作系统, 64 位操作系统就不存在这个问题。但是从前面的 “千年虫” 事件来看,人类从历史中吸取的唯一教训,就是人类不会吸取任何教训。
04 小结
对于发现的缺陷,不能仅停留在把问题解决了就完事。有时间和精力,还是需要更深层次的去了解缺陷背后的逻辑和根因是什么,触类旁通。以避免更多类似的问题发生。
在写这篇文章的时候,又想起了自己以前做报表相关的业务时,对于时间的精度特别敏感,也会遇到一些关于精度上的取舍问题,这需要我们和业务方面讨论并确认清楚,是精确到秒,还是毫秒,以避免出现数据边界的小问题。
原文链接:https://mp.weixin.qq.com/s/w5g3le-V9hdlp8vQ3ji2Ug