Redis SortedSet結構score欄位丟失精度問題解決辦法
一、問題現象
專案中採用Redis SortedSet儲存使用者的離線訊息,score值儲存的msgid(訊息ID)。msgid採用snowflake演算法生成,按照時間有序。(參看《一個海量線上使用者即時通訊系統(IM)的完整設計》)
生成的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丟失精度的問題。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556438/viewspace-2218088/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- JavaScript中解決計算精度丟失的問題JavaScript
- 請問 django admin 介面 css 丟失解決辦法?DjangoCSS
- JS大坑之19位數的Number型精度丟失問題JS
- 記錄--前端金額運算精度丟失問題及解決方案前端
- Golang浮點數精度丟失問題擴充套件包解決方案Golang套件
- JavaScript精度丟失原因以及解決方案JavaScript
- 丟失msvcr120_clr0400.dll解決辦法標題
- RocketMq訊息丟失問題解決MQ
- [Java] 浮點數的精度丟失問題與精度控制方法Java
- 完美解決方案-雪花演算法ID到前端之後精度丟失問題演算法前端
- Double BigDecimal 精度丟失總結Decimal
- AWS建立AMI映像資料丟失解決辦法
- Jison解決JS處理後端返回的Long型資料精度丟失問題JS後端
- SpringCloud解決feign呼叫token丟失問題SpringGCCloud
- Redis資料結構SortedSet底層原理詳解Redis資料結構
- npm install realm --save失敗的問題與解決辦法NPM
- 雪花演算法ID在前端丟失精度解決方案演算法前端
- 跨域問題解決辦法跨域
- vuex頁面重新整理資料丟失的解決辦法Vue
- svn相關問題解決辦法
- mybatis update並非所有欄位需要更新的解決辦法MyBatis
- WebGL著色器32位浮點數精度損失問題Web
- Nginx session丟失問題處理解決方法NginxSession
- 巧用 Base62 解決欄位太短的問題
- 前後端分離解決session跨域丟失問題後端Session跨域
- 解決RabbitMQ訊息丟失與重複消費問題MQ
- 資料庫欄位為0000-00-00的解決辦法資料庫
- VScode 更新失敗解決辦法VSCode
- npm install 失敗解決辦法NPM
- 資料庫高可靠,輕鬆解決事務丟失問題資料庫
- Vuex資料頁面重新整理丟失問題解決方案Vue
- STM32傳送串列埠資料丟失位元組的解決辦法串列埠
- 【Socket】解決UDP丟包問題UDP
- Spring Mvc Long型別精度丟失SpringMVC型別
- NPM run dev 失敗解決辦法NPMdev
- Macbook Pro Big Sur出問題解決辦法Mac
- Xshell連線Linux慢問題解決辦法Linux
- 前端inline元素間隙問題解決辦法前端inline