天天看點

網際網路面試-請簡單介紹在分布式系統中全局唯一ID的實作方式?

作者:架構師面試寶典
網際網路面試-請簡單介紹在分布式系統中全局唯一ID的實作方式?

在分布式系統中,分布式ID生成方法大緻可以分為兩類:一類是DB型,根據不同的起始位置,設定不同的步長來增長,需要考慮到服務的容錯性和可用性;第二種是基于雪花算法,将64位的資料按照不同的長度劃分成不同的段,并且每段都代表自己不同的含義,基本是按照時間戳、機器的ID值以及所在序列數進行編碼,但是這種方案需要考慮的是時鐘回撥的問題,以及由于字段過長的緩存問題。下面我們就來具體看看再實際開發中會用到的一些ID生成方案。

為什麼要使用全局唯一的ID?

在單體架構中,我們基本上采用的單資料庫來提供資料持久化的服務,并且在設計業務字段的時候業務表的ID也是從1開始遞增實作。但是随着業務的增加,系統的結構發生了變化在分布式系統中,采用類多資料庫多資料表的結構來進行資料持久化的存儲,這個時候如果在多庫多表的情況下還是采用自動增長的ID的方式就會導緻ID重複,這樣就不能保證主鍵的唯一性,如下圖所示。

網際網路面試-請簡單介紹在分布式系統中全局唯一ID的實作方式?

這個時候,如果在DB1上存在一個訂單其ID為1,那麼當第二筆訂單進入到DB2中的時候,其ID也是1。這個操作在系統上是沒有問題的,但是在使用者層面上卻是對這個操作不會有DB1和DB2的區分,他隻會認為是自己下單成功。那麼這樣就會出現從DB1 和 DB2中都會查詢出ID為1 的訂單,顯然這是不符合邏輯的,那麼針對這個問題,我們就需要采用全局唯一的ID來進行解決。下面我們就來看看集中全局唯一ID解決方案。

UUID

UUID(Universally Unique Identifier),通用唯一識别碼的縮寫,UUID是由32位數的16進制數字所組成,是以理論上存在的可能性是2的128次方的資料 。這個資料量是非常大的。可以認為是資料是夠用的。

一般生成的UUID是由8-4-4-4-12 的資料格式組成,其中32個字元和4個連字元“-”;一般情況下會将連字元删除。

目前UUID的生成方式一般有5種,并且每個方法都對應自己的應用範圍。

基于時間的UUID

這個一般是通過目前時間,随機數,以及本地的MAC位址來進行計算生成的,可以通過 org.apache.logging.log4j.core.util包中的 UuidUtil.getTimeBasedUuid()來使用或者其他包中工具。由于使用了MAC位址,是以能夠確定唯一性,但是同時也暴露了MAC位址,私密性不夠好。

DCE安全的UUID

與基于時間的UUID生成方式相同,但是它會将時間戳的前4位換成POSIX的UID或者GID。這種方式在實際的開發中非常少用。

基于名字的UUID(MD5)

基于名字的UUID是通過計算名字和名字的MD5散列值而得到,這種方式可以保證同一名稱空間中的不同名字生成的UUID是唯一的,不同名稱空間中的UUID生成也是唯一的,但是相同的名稱空間,相同的名字UUID的生成就是一樣的。

随機UUID

根據随機數,或者是僞随機數來生成的UUID,這種UUID産生重複的機率可以通過計算得到,但是這個重複的機率幾乎可以忽略不計,并且這個也是在實際工作中最經常被用到的一種方式。

基于名字的UUID(SHA1)

這個與上面的基于名字的算法是類似的隻不過是計算散列的算法從MD5變成了SHA1算法

在Java中,JDK自帶的UUID算法就是随機數生成的UUID,有興趣的讀者可以找到對應的源碼進行檢視。

雖然UUID在生成唯一ID的方面比較快速,并且幾乎是本地生成不會産生網絡開銷,但是其在使用起來還是會存在如下的一些問題

  • 不易于存儲:UUID太長,通常是一個字元串進行存儲,在很多場景中都不是太适用。
  • 資訊不安全:在上面我們提到的基于MAC位址的UUID生成規則中,可能會導緻MAC位址洩露。
  • 對MySQL索引的建立非常不利:作為資料庫的主鍵字段,在InnoDB的引擎下,UUID的無序性會導緻索引的位置的無序性,就會導緻基于B+樹建立的索引的查詢效率低下。

資料庫生成

在一個分布式系統中是不是一定要采用外部的ID生成器才能保證ID的唯一性呢?有沒有可能采用内部的資料庫自增主鍵來實作ID的唯一性操作呢?

根據上面的分析,在一個分布式系統中,重複主鍵的事情是由于每個資料庫都設定了一樣的資料起始位置和增長步長,那麼我們可不可以将每個資料庫的起始位置以及步長都設定一下,不就可以實作每個庫裡面的ID都是唯一的了麼?如下圖所示。

網際網路面試-請簡單介紹在分布式系統中全局唯一ID的實作方式?

可以将第一個庫的初始位置設定為1 并且增長步長為3 ,那麼第二條資料進入到DB1的時候,其對應的ID就是4,以此類推,DB2中的資料進入之後預設第一條資料的ID是2,并且自增步長為3,那麼第二條資料的ID就是5,以此類推。通過這種方式可以實作在三個DB中的資料唯一性。

從這裡看,這種實作方式的優勢也是非常明顯的。因為沒有用到第三方的資源,是以不會出現其他資源的損耗,并且這個ID是自增長的,是以完全可以用來實作一些沒有業務邏輯操作的ID的生成操作。

但是其缺點也是非常明顯的,如果對于有些業務來講對ID的業務性有要求的話就不好實作了,另外就是在我們上面圖中示範的時候,出現的三個DB,那麼在實際操作中對于一些高峰時段有可能會動态的添加DB這個時候就需要對這個配置進行修改,這個時候還是會出現重複ID的情況。另外就是在節點發生故障的時候,也會出現ID重複的情況。是以在實際操作中不建議使用。

基于Redis實作

Redis實作分布式唯一ID的方式主要是通過INCR和INCRBY兩個指令。這兩個指令是原子自增的。每次調用之後回報的結果就會自動加一。并且由于Redis設計之初是單線程的,是以說不會出現多線程資源競争的問題,也就完全保證了每次得到ID都是唯一的,并且都是有序的。

但是由于Redis是單機的,是以在一些高并發的業務中依然是沒有辦法保證正常的業務需求的,是以可以采用Redis叢集的方式。但是使用Redis叢集機會出現于資料庫叢集自動生成ID的時候一樣的方式,這個時候就需要通過分段和步長的方式來實作區分。

為了避免數字太大,可以講數字與目前時間的時間戳結合起來作為唯一的ID來使用,另外就是為了避免高并發的瓶頸,還可以通過Lua腳本的方式來進行操作,保證線程安全性。

Redis用來實作全局唯一ID的生成,其性能是完全可以保證的,但是也不能保證一定是高效的,由于需要使用的Redis資料庫是以才代碼中就必須要引入Redis相關的操作,這樣就會增加系統的負擔。

當然在實際的操作中,如果在其他的業務場景中已經采用了Redis叢集,本着這個業務對于Redis使用,也可以嘗試使用Redis來生成全局的唯一ID。

總結

上面我們介紹了一些基礎的分布式ID生成方式,由于篇幅太長的問題,決定在分享第二篇關于分布式ID生成相關的内容,并且在其中介紹關于業界比較常用的幾種實作方式,雪花算法、百度分布式ID生成器、美團Leaf以及Mist薄霧算法等等。

繼續閱讀