大家好,我是【架構擺渡人】,一隻十年的程式猿。這是實踐經驗系列的第十一篇文章,這個系列會給大家分享很多在實際工作中有用的經驗,如果有收穫,還請分享給更多的朋友。
前面有篇文章我們講到用時間來代替自增ID進行分頁排序,原因是因為接入了分散式ID,但是分散式ID不能夠保證有序,只能保證全域性唯一。
那麼今天我們一起來探討下,究竟能不能實現有序的分散式ID呢?
分散式ID的實現方式
號段模式
號段模式是目前用的比較多的實現分散式ID的方式,號段模式通過預先獲取一段範圍,然後全部在記憶體中進行ID的分發,效能極高。
採用號段模式實現的開源框架也有很多,比如美團的Leaf,滴滴的Tinyid。對號段模式實現原理不瞭解的小夥伴可以檢視下面的地址進行深入學習。號段模式想要實現遞增比較難,文章後面我們一起聊聊有沒有什麼方式能夠實現。
Leaf: https://github.com/Meituan-Dianping/Leaf
Tinyid: https://github.com/didi/tinyid
Snowflake
Snowflake 是 Twitter 開源的分散式ID生成演算法,在國內用的也比較多。比如百度開源的uid-generator就是基於Snowflake演算法進行改進。
uid-generator:https://github.com/baidu/uid-generator
Snowflake生成ID效能很好,而且也滿足遞增的要求。不過依賴機器上的時間,如果時間不一致就會有重複的問題。
Redis Incr
Redis 可以直接使用Incr命令進行數字的遞增,從而實現自增的ID。如果使用Redis實現分散式ID需要進行ID的持久化,否則重啟後就沒了會出現重複的問題。
既然要持久化,那就避免不了使用AOF或者RDB實現持久化。使用RDB可能會丟失資料,導致ID重複發生。使用AOF肯定要配置刷資料的模式為always,每次操作記錄都同步到硬碟上,這樣才能保證資料不丟失,但是效能較差。當然Redis 4.0還有混合模式(AOF+RDB)可以用也是一種不錯的選擇。
無論是單機還是叢集環境下,某個key必定會路由到某一個節點。雖然Redis單機也能支援10萬基本的QPS,假設你的ID需求超過了這個量級,這塊就會成為瓶頸,因為你要保證自增,沒辦法水平擴容。
號段模式想有序怎麼辦?
號段模式資料不會丟失,效能高,是一個很優秀的方案。但是號段模式沒辦法實現全域性的遞增ID,如果要實現全域性遞增,那麼就不能拆分,得有一個固定的節點,所有請求都從這個節點獲取ID才行。但是號段模式就是分段的特性,通過分段可以實現水平擴充套件,提升效能。
號段模式只能實現區域性遞增,比如我訂單表接入了分散式ID,如果用號段模式,那麼就會出現我下第一單的ID是20001,接著下第二單,ID可能就是10003。如果此時用的是ID降序排序,就會出現順序錯亂的問題,這也是之前文章裡面為什麼要用高精度的時間來排序。
這種情況下,是否只要保證同一個使用者下的資料是遞增的就可以,這就是區域性遞增。如果要想實現區域性遞增需要考慮下面幾個問題:
- 固定路由
使用者維度的遞增,就需要將使用者的請求路由到固定的ID服務節點,這樣取出來的ID就是區域性遞增。
- ID服務某個節點掛掉或者重啟
假設使用者A一直是路由到 id-service-b 上面,此時 id-service-b 掛掉了,那就需要重新找個新的節點進行路由,問題來了,路由到新的節點,假設是 id-service-c。
而此時 id-service-c 上面的ID 段可能比 id-service-b大,如果大的話就沒問題,還是遞增。如果小的話就有問題了呀,所以這個問題要解決。
如何解決上面的問題呢?
個人感覺這個問題不太好解決,因為路由到某一個節點的使用者是N,也不可能對這些使用者現有的ID進行查詢,判斷是否大於即將路由的服務的ID段。整個邏輯太複雜了。
有一個簡單的方式,就是當有固定路由需求的ID要進行路由切換的時候,全域性加鎖(此時併發量大的話會影響使用方的RT),將這個ID對應的所有段都清除,這樣重新路由的時候就又申請了新的段,新的段肯定大於之前的段,也就是遞增。
總結
其實要想實現某個需求,背後的方式有很多。如何去選擇適合,最優的方案這個是比較考驗大家平時的積累和技術的廣度。你得知道每種方案的原理是什麼,差異點在哪,哪種成本更低,哪種能夠完全符合業務需求。
原創:架構擺渡人(公眾號ID:jiagoubaiduren),歡迎分享,轉載請保留出處。
本文已收錄至學習網站 http://cxytiandi.com/ ,裡面有Spring Boot, Spring Cloud,分庫分表,微服務,面試等相關內容。