摘要
數據庫分庫分表同時因爲服務是多節點部署的,所以就會引申出分佈式系統中唯一主鍵ID的生成問題。一般我們唯一ID的有以下特性:
- 整個系統ID唯一
- ID是數字類型,而且是趨勢遞增的
- ID簡短,查詢效率快
下面我們看下常見的幾種ID生成方式
UUID/GUID
使用guid或者uuid作爲主鍵id
優點
- 代碼實現簡單。
- 本機生成,沒有性能問題
- 因爲是全球唯一的ID,所以遷移數據容易
缺點
- 每次生成的ID是無序的,無法保證趨勢遞增
- UUID的字符串存儲,查詢效率慢
- 存儲空間大
- ID無業務含義,不可讀
MySQL主鍵自增
這個方案就是利用了MySQL的主鍵自增auto_increment,默認每次ID加1。對於我們分表的業務場景中通過衛不同的表設置不同的步長以及初始值實現。
優點
- 數字化,id遞增
- 查詢效率高
- 具有一定的業務可讀
缺點
- 存在單點問題,如果mysql掛了,就沒法生成iD了
- 數據庫壓力大,高併發抗不住
雪花算法
雪花算法生成64位的二進制正整數,然後轉換成10進制的數。64位二進制數由如下部分組成:
- 1位標識符:始終是0
- 41位時間戳:41位時間截不是存儲當前時間的時間截,而是存儲時間截的差值(當前時間截 - 開始時間截 )得到的值,這裏的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的
- 10位機器標識碼:可以部署在1024個節點,如果機器分機房(IDC)部署,這10位可以由 5位機房ID + 5位機器ID 組成
- 12位序列:毫秒內的計數,12位的計數順序號支持每個節點每毫秒(同一機器,同一時間截)產生4096個ID序號
優點
- 此方案每秒能夠產生409.6萬個ID,性能快
- 時間戳在高位,自增序列在低位,整個ID是趨勢遞增的,按照時間有序遞增
- 靈活度高,可以根據業務需求,調整bit位的劃分,滿足不同的需求
缺點
- 依賴機器的時鐘,如果服務器時鐘回撥,會導致重複ID生成
Redis生成方案
利用redis的incr原子性操作自增,一般算法爲:
年份 + 當天距當年第多少天 + 天數 + 小時 + redis自增
優點
- 有序遞增,可讀性強
缺點
- 佔用帶寬,每次要向redis進行請求
應用
由於我們存在高併發場景,而且期望id是去世遞增,所以上述方法都不能完全適用我們的業務場景。我們選擇改造自增主鍵的方式。
改造數據庫主鍵自增
自增主鍵有兩個問題:
- 一旦步長定下來,不容易擴容
- 數據庫壓力大
爲什麼壓力大?是因爲我們每次獲取ID的時候,都要去數據庫請求一次。那我們可以不可以不要每次去取?思路我們可以請求數據庫得到ID的時候,可設計成獲得的ID是一個ID區間段。
這種方式如果多個節點同時發起請求獲取id區間會有併發問題,所以需要加鎖,可以通過分佈式鎖或者數據庫自身的鎖解決。雖然加鎖可以解決併發問題但是還是會有阻塞問題。此時可以通過雙buffer緩存解決。
雙buffer方案
1、當前獲取ID在buffer1中,每次獲取ID在buffer1中獲取
2、當buffer1中的Id已經使用到了100,也就是達到區間的70%
3、達到了70%,先判斷buffer2中有沒有去獲取過,如果沒有就立即發起請求獲取ID線程,此線程把獲取到的ID,設置到buffer2中。
4、如果buffer1用完了,buffer2會升級到buffer1
6、依次往返
這樣不僅達到了業務場景用的ID,都是在jvm內存中獲得的,從此不需要到數據庫中獲取了。允許數據庫宕機時間更長了。而且因爲會有一個線程,會觀察什麼時候去自動獲取。兩個buffer之間自行切換使用。就解決了突發阻塞的問題。