我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Redis SortedSet结构score字段丢失精度问题解决办法是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Redis SortedSet结构score字段丢失精度问题解决办法是什么

这期内容当中小编将会给大家带来有关Redis SortedSet结构score字段丢失精度问题解决办法是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

一、问题现象

项目中采用Redis SortedSet存储用户的离线消息,score值存储的msgid(消息ID)。msgid采用snowflake算法生成,按照时间有序。

生成的msgid有18位十进制整数,例如 215857550229364736

我们发现数值很接近的msgid,在redis中无法通过score进行区分。

举个列子,在redis中tzset结构里存入如下几条数据  

ZADD tzset 215857497028812800 test1

ZADD tzset 215857540511162369 test2

ZADD tzset 215857550229364736 test3

ZADD tzset 215857550229364737 test4

查询看一下结果 

Redis SortedSet结构score字段丢失精度问题解决办法是什么

我们发现score值采用科学计数法表示,test3,test4两个元素的score值显示是一样的。

使用score=215857550229364736 执行查询,结果如下图 

Redis SortedSet结构score字段丢失精度问题解决办法是什么

使用215857550229364736查询,结果score为215857550229364737的test4也被查出来了

用215857550229364739去查,竟然也能查出来 

Redis SortedSet结构score字段丢失精度问题解决办法是什么

这一现象给我们的系统功能带了困扰,会影响到消息同步TimeLine的精确性(参看《基于TimeLine模型的消息同步机制》)。

二、问题原因

查询相关资料发现Sorted Sets中的Score是double类型,我们的msgid是long类型。问题是long转换为double时,丢失精度。

1、snowflake算法简介

消息ID采用snowflake算法,采用64位二进制整数。二进制具体位数含义如下图。

Redis SortedSet结构score字段丢失精度问题解决办法是什么

1位,不用。二进制中最高位为1的都是负数,但是我们生成的id都使用正数,所以这个最高位固定是0

41位,用来记录时间戳(毫秒)。

如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 241−1,减1是因为可表示的数值范围是从0开始算的,而不是1。

也就是说41位可以表示241−1个毫秒的值,转化成单位年则是(241−1)/(1000∗60∗60∗24∗365)=69年

10位,用来记录工作机器id。

可以部署在1024个节点,包括5位datacenterId和5位workerId

12位,序列号,用来记录同毫秒内产生的不同id。

12位(bit)可以表示的最大正整数是4095,即可以用0、1、2、3、....4095这4096个数字,来表示同一机器同一时间截(毫秒)内产生的4096个ID序号

2、doublel数据结构

double数据的结构如下图 

Redis SortedSet结构score字段丢失精度问题解决办法是什么

3、问题定位

63bit(去掉符号位)的数转换为52bit的数,从某一位开始进行了四舍五入,导致精度下降。所以215857550229364736、215857550229364737、215857550229364739三个数据被转换为double类型后,计算机认为是相同的数。

三、解决办法

问题找到了,怎么解决呢?

id生成策略要保证整个系统生命周期类所有ID唯一,设计一个52bit的ID生成器保证ID唯一难度较大。

Redis的score数据类型更是修改不了

用52bit来表示63bit的数据一定会丢失信息,长整型long默认转换为double的方式丢失的信息会影响到业务,能不能结合业务特点自定义一种转换(映射)方式,答案是肯定的。

有以下几种想法

1、因为Redis缓存的消息最多保存15天(假设)或者最多保存多少条。能不能截去41位时间戳的部分高位,确保Redis缓存时间周期内时间戳长度够用就行呢?计算了一下长度 log(15*24*60*60*1000)=30.2,大约30位二进制数即可在现有规则下表示15天时间。所以将41位时间戳的前11位屏蔽掉,可以节约11位二进制信息。这样63bit刚好能用52bit来表示。

然而这个方式有个致命问题,当15天时间周期到了后,时间戳会变得特别小(新的周期),这导致上一个周期后边的数据Score值大于新周期。消息顺序混乱了,会导致拉离线丢消息,这不能接受!

2、去掉10bit工作机id和序列号的最高位bit。

去掉这11bit,不会对消息的顺序造成影响,但是可能造成score数值冲突(相同)。分析一下score冲突的可能性。

(1)12bit序列号能表示4096个数。去掉最高位,能表示2048个数。所以单个msgid生成节点(dispatch模块)每毫秒,每个用户要超过2048条消息,才可能出现score重复。这个基本不可能发生。

(2)去掉10bit工作机id号,需要同一毫秒,同一用户在不同的dispatch节点都接收到消息,score才可能冲突。即使出现这种情况,由于12位序列号我们做了模128的随机分布(解决分库问题),即使出现同一毫秒不同disptch生成同一用户msgid的情况下,score冲突的概率还要除以 128*128。这个概率非常低。

(3)即使出现了score冲突(两条消息有相同score),最多造成拉取离线消息多拉取相同score的消息(本来一次拉取10条离线,结果可能拉到11条),对业务也没有影响。

因此采用去掉10bit工作机id和序列号的最高位bit将63bit(不含符号位)的msgid转换成52bit的score对业务上没有影响。同时解决了redis sorted set丢失精度的问题。 

因此采用去掉10bit工作机id和序列号的最高位bit将63bit(不含符号位)的msgid转换成52bit的score对业务上没有影响。同时解决了redis sorted set丢失精度的问题。

上述就是小编为大家分享的Redis SortedSet结构score字段丢失精度问题解决办法是什么了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Redis SortedSet结构score字段丢失精度问题解决办法是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

目录