文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

这期内容当中小编将会给大家带来有关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

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯