研究分布式唯一ID生成,看完這篇就夠


  很多大的互聯網公司數據量很大,都采用分庫分表,那么分庫后就需要統一的唯一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可以直接拿着使用的。

  如果有不對的地方,還望指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM