不少大的互聯網公司數據量很大,都採用分庫分表,那麼分庫後就須要統一的惟一ID進行存儲。這個ID能夠是數字遞增的,也能夠是UUID類型的。mysql
若是是遞增的話,那麼拆分了數據庫後,能夠按照id的hash,均勻的分配到數據庫中,而且mysql數據庫若是將遞增的字段做爲主鍵存儲的話會大大提升存儲速度。可是若是把訂單ID按照數字遞增的話,別人可以很容易猜到你有多少訂單了,這種狀況就能夠須要一種非數字遞增的方式進行ID的生成。算法
想到分佈式ID的生成,你們可能想到採用Redis進行生成ID,使用Redis的INCR命令去生成和獲取這個自增的ID,這個沒有問題,可是這個INCR的生成QPS速度爲200400(官網發佈的測試結果),也就是20W這樣子,若是QPS沒有超過這些的話,顯然使用Redis比較合適。sql
那麼咱們對於要達到高可用,高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能夠直接拿着使用的。
若是有不對的地方,還望指正。