您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關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
查詢看一下結果
我們發現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字段丟失精度問題解決辦法是什么了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。