很多大的網際網路公司資料量很大,都採用分庫分表,那麼分庫後就需要統一的唯一ID進行儲存。這個ID可以是數字遞增的,也可以是UUID型別的。
如果是遞增的話,那麼拆分了資料庫後,可以按照id的hash,均勻的分配到資料庫中,並且mysql資料庫如果將遞增的欄位作為主鍵儲存的話會大大提高儲存速度。但是如果把訂單ID按照數字遞增的話,別人能夠很容易猜到你有多少訂單了,這種情況就可以需要一種非數字遞增的方式進行ID的生成。
想到分散式ID的生成,大家可能想到採用Redis進行生成ID,使用Redis的INCR命令去生成和獲取這個自增的ID,這個沒有問題,但是這個INCR的生成QPS速度為200400(官網釋出的測試結果),也就是20W這樣子,如果QPS沒有超過這些的話,顯然使用Redis比較合適。
那麼我們對於要達到高可用,高QPS,低延遲我們有沒有更好的想法呢。接下來一起看一下snowflake演算法,由twitter公司開源的雪花演算法。
snowflake一共64位:
1. 第一位不用。
2. 41位是時間戳。 2^41以毫秒為單位的話,可得到69年,非常夠用了。
3. 10位位工作機器,可以有2^10=1024個工作節點,有的公司將其拆分為5位工作中心編碼,5位分給工作機器。
4. 最後12位用於生成遞增資料共4096個數。
如果用這個理論上的QPS上的QPS為409W/S。
這種方式的優點為:
1. QPS非常高,效能也非常夠。高效能條件也滿足了。
2. 不需要依賴其他第三方的中介軟體,比如Redis。少了依賴,可用率提高了。
3. 可以根據自己定製進行調節。也就是裡邊的10位進行自由分配。
缺點:
1. 此種演算法很依賴時鐘,假如時鐘進行回撥了,將有可能生成相同的ID。
UUID是採用32位二進位制資料生成的,它生成的效能非常好,但是它是基於機器MAC地址生成的,而且不是分散式的,所以不是我們們討論的範疇。
下面我們們看一下一些大公司的分散式ID實現機制,通過生成建立一張表,採用8個Byte, 64位進行儲存使用,用這張表記錄所產生ID的位置,比如ID從0開始,然後使用了1000個,那麼資料庫裡邊記錄裡邊的最大值是一千,同時還有個步長值,比如1000,那麼獲取下一個值得時候最大值為2001,即最大的沒有使用的值。
具體的實現步驟如下:
1. 提供一個生成分散式ID的服務,這個ID的服務是讀取資料庫裡邊的值和步長值計算生成需要的值和範圍,然後服務消費方拿到後進行將號段儲存到快取中使用。
2.當給到服務呼叫方之後,資料庫立即更新資料。
這種情況下的優點為:
1. 容災效能好,如果DB出現問題,因為資料放到記憶體中,還是可以支撐一段時間。
2. 8個Byte可以滿足業務生成ID使用。
3. 最大值可以自己定義,這樣有些遷移的業務還可以自己定義最大值繼續使用。
當然缺點也存在:
1. 當資料庫掛了整個系統將不能使用。
2. 號段遞增的,容易被其他人猜到。
3. 如果很多服務同時訪問獲取這個ID或者網路波動導致資料庫IO升高的時候,系統穩定性會出現問題。
然後針對上述情況的解決方法是他們採用了雙快取機制,即將號碼段讀取到記憶體中之後開始使用,當使用到了10%的時候重新啟動一個新執行緒,然後當一個快取用完了之後去用另一塊快取的資料。當另一個快取的資料達到10%的時候再重啟激動一個新執行緒獲取,依次反覆。
這樣做的好處是避免同時訪問大量資料庫,導致I/O增多。同時可以通過兩個快取段解決了單一快取導致很快用完的情況。當然把這個號段設定成QPS大小的600倍,這樣資料庫掛了10-20分鐘內還是可以繼續提供服務的。
以上一直提到了一個問題,就是ID遞增,我們們如何解決這個問題呢。就是採用snowflake,然後解決裡邊的時鐘問題,有些公司採用ZK去比較當前workerId也就是節點ID使用的時間是否有回撥,如果有回撥就進行休眠固定時間,看是否能趕上時間,如果能趕上的話,繼續生成ID,如果一直沒有趕上達到某個值得話,那麼就報錯處理。因為中間10位是表示不同的節點,那麼不同的節點生成的ID就不會存在遞增的情況。
這些思路都是某公司已經實現了的,如果有興趣繼續研究的話,那麼在GITHUB上搜尋下開源的Leaf可以直接拿著使用的。
如果有不對的地方,還望指正。