Redis Zset的精度问题分析及解决方案
本篇博客的视频教程首发于 Youtube:科技小飞哥,加入 电报粉丝群 获得最新视频更新和问题解答。
背景
最近使用Redis的SortedSet,使用int64作为score时遇到了一些预料之外的情况,在此总结一下。
介绍
项目中采用Redis SortedSet存储用户一些信息,score值存储的msgid(消息ID)。msgid采用snowflake算法生成,按照时间有序。
Snowflake的算法在我之前的博客里面有讲解: Snowflake算法
我们这边生成的msgid是根据snowflake算法生成的int64位整数。例如:215857550229364734
我们发现数值很接近的msgid,在redis中无法通过score进行区分。
举个列子,在redis中tzset结构里存入如下几条数据。
ZADD tzset 215857550229364734 test1
ZADD tzset 215857550229364735 test2
ZADD tzset 215857550229364736 test3
ZADD tzset 215857550229364737 test4
ZADD tzset 215857550229375123 test5
查询看一下结果
127.0.0.1:6379> zrange tzset 0 -1 WITHSCORES
1) "test1"
2) "2.1585755022936474e+17"
3) "test2"
4) "2.1585755022936474e+17"
5) "test3"
6) "2.1585755022936474e+17"
7) "test4"
8) "2.1585755022936474e+17"
9) "test5"
10) "2.1585755022937514e+17"
我们发现score值采用科学计数法表示,test1,test2,test3,test4几个元素的score值显示是一样的。
使用score=215857550229364735
执行查询,结果如下:
127.0.0.1:6379> zrangebyscore tzset 215857550229364735 215857550229364735
1) "test1"
2) "test2"
3) "test3"
4) "test4"
127.0.0.1:6379> zrangebyscore tzset (215857550229364735 215857550229375123
1) "test5"
发现与预期不符。
问题描述
这导致了我们使用socre做分页查找的时候,(一页10个,下一页使用上一页最后一个socre来继续查找)。会导致其中的一些数据丢失。 这些都是因为SortedSet的类型是double64的浮点数,存在精度问题。
贴上Redis官方文档ZADD指令的描述里面的一段话。 Redis ZAdd
Range of integer scores that can be expressed precisely
Redis sorted sets use a double 64-bit floating point number to represent the score. In all the architectures we support, this is represented as an IEEE 754 floating point number, that is able to represent precisely integer numbers between -(2^53) and +(2^53) included. In more practical terms, all the integers between -9007199254740992 and 9007199254740992 are perfectly representable. Larger integers, or fractions, are internally represented in exponential form, so it is possible that you get only an approximation of the decimal number, or of the very big integer, that you set as score.
简单来说,double64位的浮点数只有53bit
的精度。所以使用int64的大整数是需要注意,这个大整数的值不能大于这个最大值。
对于snowflake算法,之前的文章中有讲过。它是使用41+10+12=63
位的大整数。在此场景下是有精度问题的。
注:使用js也会有53bit的精度问题,几乎所有的编程语言都采用了 IEEE-754
双精度64 位浮点数表示法,任何使用二进制浮点数的编程语言都会有这个问题.
- sign bit(符号): 用来表示正负号
- exponent(指数): 用来表示次方数
- mantissa(尾数): 用来表示精确度
也就是说一个数字的范围只能在 -(2^53 -1) 至 2^53 -1 之间。
这个精度问题在很多业务上都会出现,很多时候前后端交互的时候作为web数据传输,大多数都会需要用到json传输数据。所以设计的数字类型就需要保证在53bit
之内。不然就会导致丢失精度。
解决方案
可以改造一下score的生成规则,比如我就修改了各个部分的长度。
- 修改timestamp的精度,由millionseconds改为seconds.
- 修改node的长度,由10位改为9位。(1024->512).
这样总长度就变为 32+9+12=53位。 当然也可以根据需求随机组合。比如把timestamp改为10millionseconds级别,或者100millionseconds级别。然后把step的精度变小一点。
<全文完>