天天看點

雲客Drupal源碼分析之通用唯一識别碼UUID

先來看一個問題:假設一個網絡系統每秒鐘需要儲存數十萬來自使用者送出的資訊,并配置設定一個id給每條資訊用于以後唯一辨別它,那麼怎麼産生這個id呢?不能重複又要足夠快以支援高并發,有這麼強大的單台伺服器嗎?即便有,随着并發加大也是很難滿足的,看來從設計上不能依賴于單台伺服器。此外有些資訊對象會跨系統存在,由于業務原因,需要進行全局唯一辨別,比如一個業務單号,業務隻關注這個業務單本身而不關注目前是哪台伺服器在處理這個業務;這些就是uuid存在的原因。

UUID:通用唯一識别碼Universally Unique IDentifier,有時也被稱為GUID全局唯一識别碼Globally Unique IDentifier,最初用于阿波羅網絡計算系統,後來開源軟體基金會用于許多分布式計算環境中,也用在微軟window平台裡,它是一個用以解決跨時空唯一性辨別問題的東西,所謂跨時空表示在有限的時間和空間内,能夠保證産生的id是獨一無二的,這樣一來就能解決上文提到的問題,但這裡有個關鍵詞“保證”需要注意,其實沒有絕對的東西,隻要機率極其低那麼我們可以“保證”,這對于處女座的人來說可能是一種煎熬,但願看完UUID機制會得到安慰,接下來看一下UUID如何做到機率極其低,到底有多低。

UUID參考:

關于UUID的規則在rfc4122中描述,詳情見:

http://www.rfc-editor.org/rfc/rfc4122.txt

關于這篇RFC有一個勘誤,是關于位元組序的,見:

http://www.rfc-editor.org/errata_search.php?rfc=4122&eid=3546

UUID:

UUID是一個有128比特的位串(16位元組),以十六進制字元的方式來表示,格式為8-4-4-4-12

正則描述為:'[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}'

如:34415db0-ed84-48e5-8aae-9d7e03bb45e9

這128比特的位串具備一定的格式,從第一位順序描述如下:

time_low :4位元組32位,表示時間的低字段

time_mid :2位元組16位,表示時間的中字段

time_hi_and_version:2位元組16位,前4位表示uuid建立類型的版本(見後),其餘位表示時間的高字段

clk_seq_hi_res:1位元組8位,前2到3位表示uuid版本變體(見後),其餘位表示時鐘序列高字段

clk_seq_low:1位元組8位,表示時鐘序列低字段

node:6位元組48位,表示空間上的識别辨別

uuid的設計目的是去中心化,不論世界上哪台計算機在什麼時間産生的uuid需是唯一的(實際上是重複機率極其低),從上面可以看到有60位來表示時間,精度是100納秒級别,目前伺服器程式運作速度大多在毫秒級别,在同一台計算機上同一程式可以保證不停生成uuid也是不同的(在rfc4122裡面也描述了超高頻率擷取uuid的解決辦法),但這裡有一個問題,計算機的時間可能重複,比如有些裝置掉電後會丢失時間,這樣一來産生相同uuid的幾率就變大了,是以引入時鐘序列字段克服這個問題,在系統初始化時時鐘序列字段應該是一個随機值,解決了時間上可能相同的問題,再解決空間上相同的問題,node有48位,典型的它可以是網卡的MAC位址、IP位址、主機名、url等,唯一辨別一台裝置的辨別,以此避免空間上的重複。

有時候軟體不能擷取到時間或者裝置辨別,那怎麼辦呢,可以采用随機數的方式,那麼如何識别以何種方式建立的uuid?這就是前文提到的uuid建立類型版本字段的作用,它有4位,目前定義了5種建立方式,網上摘錄如下:

UUID Version 1:基于時間的UUID

基于時間的UUID通過計算目前時間戳、随機數和機器MAC位址得到。由于在算法中使用了MAC位址,這個版本的UUID可以保證在全球範圍的唯一性。但與此同時,使用MAC位址會帶來安全性問題,這就是這個版本UUID受到批評的地方。如果應用隻是在區域網路中使用,也可以使用退化的算法,以IP位址來代替MAC位址--Java的UUID往往是這樣實作的(當然也考慮了擷取MAC的難度)。

UUID Version 2:DCE安全的UUID

DCE(Distributed Computing Environment)安全的UUID和基于時間的UUID算法相同,但會把時間戳的前4位置換為POSIX的UID或GID。這個版本的UUID在實際中較少用到。

UUID Version 3:基于名字的UUID(MD5)

基于名字的UUID通過計算名字和名字空間的MD5散列值得到。這個版本的UUID保證了:相同名字空間中不同名字生成的UUID的唯一性;不同名字空間中的UUID的唯一性;相同名字空間中相同名字的UUID重複生成是相同的。

UUID Version 4:随機UUID

根據随機數,或者僞随機數生成UUID,實際中往往使用密碼學上的強随機數生成器。

UUID Version 5:基于名字的UUID(SHA1)

和版本3的UUID算法類似,隻是散列值計算使用SHA1(Secure Hash Algorithm 1)算法。

前文還提到clk_seq_hi_res中有兩到三位的版本變體Variant字段,它是什麼意思呢?可以了解為uuid字段布局的版本号,在uuid中全部字段位的含義都依賴于這個字段值的設定,相當于是uuid機制的版本号,以上解釋的字段含義目前定為10,隻用了兩個位;Variant字段的其他值或為應對未來變化而保留,或為相容性考慮。

通過上面的介紹,除開建立版本的4位和變體字段的兩位,還有122位,這是一個很大的數字(5後面加36個零),系統産生相同uuid的機率是非常非常小的,這個是一個什麼概念呢,形象的說:

中彩票頭獎的機率是千萬分之一級别,假設每秒買一張彩票,一直買10的21次方年,全都中頭獎,可能發生這樣的事情嗎?

或者1千億台計算機,每台每秒産生1億個uuid,需要一百六十億年才會重複

這樣的機率就是uuid重複的機率,是否能讓處女座的人們安心的認為uuid能擔保不重複呢?

drupal中的uuid:

在drupal中實體用到了uuid,如果網站規模做的很大時,會涉及資料庫分表分庫,此時uuid将帶來幫助,系統中uuid由以它命名的uuid服務提供,如下:

容器服務id:uuid

類:Drupal\Component\Uuid\Php

使用方式:echo \Drupal::service("uuid")->generate();

程式代碼解釋如下:

class Php implements UuidInterface {
  public function generate() {
    // 使用密碼級别的随機數生成器産生高品質随機性,将二進制轉為16進制,$hex有32個字元
    $hex = bin2hex(Crypt::randomBytes(16));
    // 一個位元組有兩個十六進制字元,提取8個十六進制字元,4位元組32位比特,表示時間低字段
    $time_low = substr($hex, 0, 8);
    //兩位元組表示時間中字段
    $time_mid = substr($hex, 8, 4);
    //此處字元4(不是數字)表示uuid建立版本為4,意指使用随機數生成,占用4比特位
    $time_hi_and_version = '4' . substr($hex, 13, 3);
    // 提取8比特位,并轉換為十進制整數
    $clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 2), 16, 10);
    //将前兩位設定為0
    $clock_seq_hi_and_reserved &= 0b00111111;
    //将前兩位設定為10
    $clock_seq_hi_and_reserved |= 0b10000000;
    //提取1位元組(8位)做時鐘序列低位
    $clock_seq_low = substr($hex, 18, 2);
    //餘下的6位元組48位作為裝置辨別
    $node = substr($hex, 20);
    //格式化輸出
    $uuid = sprintf('%s-%s-%s-%02x%s-%s',
      $time_low, $time_mid, $time_hi_and_version,
      $clock_seq_hi_and_reserved, $clock_seq_low,
      $node
    );
    return $uuid;
  }
}
           

可以看出uuid的第三段的第一個字元始終為4,是因為drupal使用随機數的方式來生成uuid,既沒有用到時間也沒有用到裝置辨別,第四段的第一個字元為二進制10**表示的值,那代表目前uuid的版本類型

在drupal中uuid是以元件方式提供,同時還提供了其他幾種生成方式,但需要擴充支援

如果需要驗證一個uuid可使用:

Drupal\Component\Uuid\Uuid:: isValid($uuid);

傳回bool值,表示是否符合uuid的特征。

我是雲客,【雲遊天下,做客四方】,聯系方式見首頁,歡迎轉載,但須注明出處