共計 2563 個字符,預計需要花費 7 分鐘才能閱讀完成。
行業資訊
數據庫
Redis SortedSet 結構 score 字段丟失精度問題解決辦法是什么
這期內容當中丸趣 TV 小編將會給大家帶來有關 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 丟失精度的問題。
上述就是丸趣 TV 小編為大家分享的 Redis SortedSet 結構 score 字段丟失精度問題解決辦法是什么了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注丸趣 TV 行業資訊頻道。