一 什麼是分布式唯一ID,有些什麼需求
在分布式系統中,要求某些ID不能重複,必須唯一。他需要滿足以下特點:
第一:全局唯一,不能重複
第二:遞增性:下一個ID一定比上一個大
第三:安全性:不得暴露與業務相關的資訊,防止使用者惡意擷取
二 有哪些分布式唯一主鍵生成方案,各自優缺點
2.1 UUID
優點:簡單
缺點:第一,16位元組大小,存儲成本高,影響寫入速度;第二,最關鍵是的做主鍵非常不合适,在因為不是趨勢遞增,那麼在主鍵索引中就不是連續的,那麼插入資料的時候就是離散的存儲,導緻頁分裂非常頻繁。第三,查詢的時候,比較UUID花的時間比數字要長,對查詢也不是很友好
2.2 資料庫主鍵自增
通過資料庫自帶的主鍵自增功能,也可以實作全局唯一。專門弄一張表,每請求一次,産生一條記錄,産生一個主鍵。
優點:第一,資料庫自帶功能,比較友善;第二,數字類型,存儲成本小;第三,産生的主主鍵是順序遞增的,符合InnoDB索引的要求
缺點:第一,強依賴資料庫,存在單點問題。如果資料庫不可用,那麼就會導緻整個系統不可用;第二,單個MySQL産生ID是可能會有性能瓶頸
2.3 利用Redis生成唯一ID
通過Redis的incr指令實作自增功能,他本身就是一個分布式資料庫,是以可以實作全局唯一ID。
優點:第一,數字類型,存儲成本低;第二, 産生的主鍵也是遞增的;第三,産生ID的性能高
缺點:第一,如果系統中沒有使用Redis,單獨引入會增加系統複雜度;第二,單個Redis也存在單點問題
2.4 雪花算法(snowflake)
雪花算法會産生一個64bit的數字作為分布式唯一ID,由實際戳+機器号+序列号組成,第一位是0,表示符号位;第1-40合計41位表示時間戳;第42-51共10位,表示機器ID,意味着最多可以部署在1024台機器上面,第52-63共12位表示這個機器上的自增序列,表示每一毫秒在每台機器上可以産生4096個唯一ID。如圖所示:

雪花算法實作方式:
優點:第一,數字類型,而且隻有64位,存儲成本低;第二,每一秒鐘可以産生4096000個ID,性能是很高的;第三,産生的唯一ID也是順序遞增的,
缺點:雪花算法是基于時間計算的,如果存在時鐘回撥的問題,那依然有可能産生重複ID
三 雪花算法如何解決時鐘回撥問題
我們知道,如果伺服器向時間伺服器進行時鐘校準,有可能會發生時間回撥,那麼這樣的話就有産生的ID既可能不是遞增順序,也可能會重複。那如何解決這個問題呢?既然受時鐘時鐘回撥這個問題,那我們想辦法在算法中規避時鐘回撥。
3.1 抛出異常,人為處理
當比較目前時間和上次産生ID的時間戳時候,如果目前時間小于上次産生ID的時間戳,則認為産生了時間回撥,此時抛出異常。百度的雪花算法實作中,DefaultUidGenerator就是這麼幹的。
還有一種做法就是,如果發生時鐘回撥,則等待一段時間,等待時間進行校正。然後再檢查目前時間是不是小于上次産生ID的時間戳,如果還是小于,說明還在受時鐘回撥影響,然後再抛出異常;否則可以正常産生ID了。美團的Leaf架構中雪花算法就是這麼做的,然後通過Zookeeper的持久化順序節點,建立workerId,即便每次重新開機,workerId也不會變。
優點:簡單
缺點:實作不優雅,需要人工介入,生産環境不建議這麼用
3.2 不使用時鐘回撥
通過資料庫主鍵産生一批或者一段資料,然後在記憶體中進行儲存,有請求到來的時候直接從記憶體取出配置設定,既避免了時鐘回撥問題,又避免了資料庫自增ID的性能瓶頸(不用來一個請求,請求一次資料庫,然後産生一個ID)。美團Leaf架構中segment模式就是這麼幹的。
優點:避免了時鐘回撥的影響,而且性能還不錯
缺點:還是對資料庫強依賴,而且需要資料庫解決單點問題
3.3 時鐘自增,不再和系統時鐘進行比較
當初次擷取目前時間後,每次上一個ID産生之後需要更新時間戳,此時更新時間戳不再使用系統目前時間,而是在之前的目前時間+1,這樣的話,既可以保證産生的ID是有序的,而且還不會受時鐘回撥的影響,隻要你的ID對時間沒有業務上的關聯,就沒什麼影響。而且百度的CachedUidGenerator底層就是這麼幹的,隻不過通過了兩個環形數組(RingBuffer)實作了批量産生ID的功能,避免每次請求才進行産生,提升性能。
CachedUidGenerator有兩個RingBuffer, 數組中每項叫做slot, 一個是存儲ID的,一個是存儲ID狀态的。存儲狀态的RingBuffer有兩個值0和1,0表示CNAPUT, 1表示CANTAKE,初始狀态都是0,表示可以放。當存放ID的RingBuffer對應的slot放一個産生的ID的時候,則會在狀态RingBuffer将狀态置為1。
Tail指針表示已經添加到什麼位置,Cursor指針表示已經消費到什麼位置,每當消費一個,就把,如果Cursor趕上了Tail說明沒有ID消費的,為了避免這種情況,CachedUidGenerator設定了一個門檻值,如果RingBuffer中已經消費了50%的ID則起線程産生ID往RingBuffer中放,直到填滿為止。