Redis Zset Score 精度问题
在开测项目服务端的开发中,redis 的 zset 是常用的数据结构。因为它元素不重复且每个元素都有一个分数的特点,经常作为有序队列和元素排序来使用,排序的方式自然是通过每个元素的 score 的大小。
score 的数据类型
在计算机中,字符都是可以比较大小的,那么 score 的数据类型是不是只要是字符型就可以的呢?答案是否定的。如下图,当我们使用非数字类型的时候会提示“(error) value is not a valid float”的错误信息。同时这个错误信息告诉我们,score 类型是浮点型的数据。
既然是浮点型的数据都可以,那么我们使用 int,long,float,double 等数字类型,当然都是可以的。但是浮点型数据自然都是有精度的,且会影响我们的业务开发。
Ps: 在相关的参考资料中有说是 double 类型的,有说是数字转成字符串类型存到 redis 中,查询时返回是字符串类型,从结果上观察都是成立的。但是在 Jedis 驱动中入参和出参使用的都是 double 数据类型。
java 的数据精度与 redis 的数据精度
先看一个有趣的例子,如下图,使用 java 代码,声明两个 double 的变量 a 和 b,值都是 27 个数字,a 的末位数字比 b 的末位数字大 1,其他位置的数字相同,a 是大于 b 的,但是代码输出的结果是 a 等于 b,a 和 b 也变成了 18 个有效数字的科学计数。
除了上面的问题,在项目中用到 double 类型数据四则运算时,经常出现精度丢失的问题,总是在一个正确的结果左右偏 0.0000**1。
其输出结果如下:
在商业型软件中,这种精度问题,会有很大的影响。在《Effective Java》这本书提供了解决方法,float 和 double 只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。但是在 Jedis 驱动中,使用的是 double 作为形参的,我们执行像 zset 中插入 3.14 的操作,代码如下。
我们通过 redis-cli 去查看的时候,结果显示的是 3.1400000000000001,但是 java 读取出来的时候显示的是 3.14。
通过 debug,我们发现,并不是数据插入时导致的精度问题,而是从 redis 中读取的时候出现的问题,由此我们可以猜测是 redis 自身的浮点型精度的问题。
我们可以通过 redis-cli 来验证一下:
果然如前面猜测的一样,redis 自身的浮点型精度是有问题的,并且做 zincrby 的时候也是有精度问题的。
开测中使用的一个例子
笔者在项目中,目前没有遇到因为精度而导致的问题,只是在开测的服务端开发中,会有使用时间戳作为 score 来进行任务的排序,时间戳由 14 位数字组成的长整型,向上转型成 double,存入 redis 中会变成成科学计数法,为了避免精度丢失的问题,才进行了相关实验。
经过实验,当 zset 中的长整型数字位数超过 17 位的时候才会出现精度丢失的问题,如下图。
此结果与 java 的 double 类型保持一致,所以使用时间戳作为分数不会造成精度丢失的问题,同时,我们应该避免使用超过 17 位的数字作为分数。如果不得不使用,笔者认为一般是数据量特别大、使用 uuid,或者是精度要求比较高,这些情况可以通过一些算法做预处理,只要达到预期的效果就行。
小结
本文对 redis 的 zset 的 score 的数据精度问题做了简单的分析和总结,希望能在以后的开发中,能够避免因为 score 精度而引起业务问题。
更多学习资料戳下方
评论