这期内容当中小编将会给大家带来有关redis SortedSet结构score字段丢失精度问题解决办法是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
成都创新互联公司技术团队10年来致力于为客户提供成都网站设计、做网站、成都外贸网站建设公司、成都品牌网站建设、网络营销推广、搜索引擎SEO优化等服务。经过多年发展,公司拥有经验丰富的技术团队,先后服务、推广了上1000家网站,包括各类中小企业、企事单位、高校等机构单位。
一、问题现象
项目中采用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
查询看一下结果
我们发现score值采用科学计数法表示,test3,test4两个元素的score值显示是一样的。
使用score=215857550229364736 执行查询,结果如下图
使用215857550229364736查询,结果score为215857550229364737的test4也被查出来了
用215857550229364739去查,竟然也能查出来
这一现象给我们的系统功能带了困扰,会影响到消息同步TimeLine的精确性(参看《基于TimeLine模型的消息同步机制》)。
二、问题原因
查询相关资料发现Sorted Sets中的Score是double类型,我们的msgid是long类型。问题是long转换为double时,丢失精度。
1、snowflake算法简介
消息ID采用snowflake算法,采用64位二进制整数。二进制具体位数含义如下图。
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数据的结构如下图
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字段丢失精度问题解决办法是什么了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注创新互联行业资讯频道。