UUID詳細介紹

什麼是UUID

UUID 是指(Universally Unique Identifier)通用唯一識別碼,128位。RFC 4122描述了具體的規範實現。

現實問題

我們開發的時候,數據庫表總會有一個主鍵,以前我們可能會使用自增的數字作爲主鍵。這樣做去確實查詢的時候比較快, 但是在做系統集成或者數據遷移的的時候就麻煩了。這是id就有可能重複了。那麼有什麼比較好的方法解決這一問題呢? 於是jdk1.5出了UUID這個類來生成唯一的字符串標識。

UUID作用

UUID 的目的是讓分佈式系統中的所有元素都能有唯一的識別信息。如此一來,每個人都可以創建不與其它人衝突的 UUID,就不需考慮數據庫創建時的名稱重複問題。其作用視場景而定。

目前最廣泛應用的 UUID,即是微軟的 Microsoft's Globally Unique Identifiers (GUIDs),而其他重要的應用, 則有 Linux ext2/ext3 檔案系統、LUKS 加密分割區、GNOME、KDE、Mac OS X 等等。

UUID定義

UUID使用16進製表示,共有36個字符(32個字母數字+4個連接符"-")組成,格式爲8-4-4-4-12 ;【一個16進制只代表4個bit,所以是(8+4+4+4+12)*4=128位;】

是由一組32位數的16進制數字所構成,故 UUID 理論上的總數爲16^{32} = 2^{128},約等於3.4 \times 10^{123}

也就是說若每納秒產生1百萬個 UUID,要花100億年纔會將所有 UUID 用完。

格式:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

示例:
6d25a684-9558-11e9-aa94-efccd7a0659b

格式中M和N都有具體的含義

數字 M的四位表示 UUID 版本,當前規範有5個版本,M可選值爲1, 2, 3, 4, 5

數字 N的一至四個最高有效位表示 UUID 變體( variant ),有固定的兩位10xx因此只可能取值8, 9, a, b。

這5個版本使用不同算法,利用不同的信息來產生UUID,各版本有各自優勢,適用於不同情景。具體使用的信息

  • version 1, date-time & MAC address
  • version 2, date-time & group/user id
  • version 3, MD5 hash & namespace
  • version 4, pseudo-random number
  • version 5, SHA-1 hash & namespace

故UUID每個版本不是根據精度區分的,Version5並不會比Version1精度高,在精度上,大家都能保證唯一性,重複的概率近乎於0

version 1——date-time  & MAC address

基於時間戳及MAC地址的UUID實現。它包括了48位的MAC地址和60位的時間戳,

接下來使用ossp-uuid命令行創建5個UUID v1。(在Mac安裝brew install ossp-uuid)

uuid -n 5 -v1
5b01c826-9561-11e9-9659-cb41250df352
5b01cc7c-9561-11e9-965a-57ad522dee7f
5b01cea2-9561-11e9-965b-a3d050dd0f99
5b01cf60-9561-11e9-965c-1b66505f58da
5b01d118-9561-11e9-965d-97354eb9e996

肉眼一看,有一種所有的UUID都很相似的感覺,幾乎就要重複了!怎麼回事?

其實v1爲了保證唯一性,當時間精度不夠時,會使用13~14位的clock sequence來擴展時間戳,比如:

當UUID的生產成速率太快,超過了系統時間的精度。時間戳的低位部分會每增加一個UUID就+1的操作來模擬更高精度的時間戳,換句話說,就是當系統時間精度無會區分2個UUID的時間先後時,爲了保證唯一性,會在其中一個UUID上+1。所以UUID重複的概率幾乎爲0,時間戳加擴展的clock sequence一共有74bits,(2的74次方,約爲1.8後面加22個零),即在每個節點下,每秒可產生1630億不重複的UUID(因爲只精確到了秒,不再是74位,所以換算了一下)。

相對於其它版本,v1還加入48位的MAC地址,這依賴於網卡供應商能提供唯一的MAC地址,同時也可能通過它反查到對應的MAC地址。Melissa病毒就是這樣做到的。

Version2(date-time Mac address)

這是最神祕的版本,RFC沒有提供具體的實現細節,以至於大部分的UUID庫都沒有實現它,只在特定的場景(DCE security)纔會用到。所以絕大數情況,我們也不會碰到它。

Version3,5(namespace name-based)

V3和V5都是通過hash namespace的標識符和名稱生成的。V3使用MD5作爲hash函數,V5則使用SHA-1

因爲裏面沒有不確定的部分,所以當namespace與輸入參數確定時,得到的UUID都是確定唯一的。比如:

uuid -n 3 -v3 ns:URL http://www.baidu.com
2f67490d-55a4-395e-b540-457195f7aa95
2f67490d-55a4-395e-b540-457195f7aa95
2f67490d-55a4-395e-b540-457195f7aa95

可以看到這3個UUID都是一樣的。

具體的流程就是

  1. 把namespace和輸入參數拼接在一起,如"http/http://wwwbaidu.com" ++ "/query=uuid";
  2. 使用MD5算法對拼接後的字串進行hash,截斷爲128位;
  3. 把UUID的Version和variant字段都替換成固定的;
  4. 如果需要to_string,需要轉爲16進制和加上連接符"-"。

把V3的hash算法由MD5換成SHA-1就成了V5。

Version4(random)

這個版本使用最爲廣泛:

uuid -n 5 -v4
a3535b78-69dd-4a9e-9a79-57e2ea28981b
a9ba3122-d89b-4855-85f1-92a018e5c457
7c59d031-a143-4676-a8ce-1b24200ab463
533831da-eab4-4c7d-a3f6-1e2da5a4c9a0
def539e8-d298-4575-b769-b55d7637b51e

其中4位代表版本,2-3位代表variant。餘下的122-121位都是全部隨機的。即有2的122次方(5.3後面36個0)個UUID。一個標準實現的UUID庫在生成了2.71萬億個UUID會產生重複UUID的可能性也只有50%的概率:

 

這相當於每秒產生10億的UUID,持續85年,而把這些UUID都存入文件,每個UUID佔16bytes,總需要45 EB(exabytes),比目前最大的數據庫(PB)還要大很多倍。

UUID的重複概率

Java中 UUID 使用版本4進行實現,所以由java.util.UUID類產生的 UUID,128個比特中,有122個比特是隨機產生,4個比特標識版本被使用,還有2個標識變體被使用。利用生日悖論,可計算出兩筆 UUID 擁有相同值的機率約爲



其中x爲 UUID 的取值範圍,n爲 UUID 的個數。

以下是以 x = 2^{122} 計算出n筆 UUID 後產生碰撞的機率:

換句話說,每秒產生10億筆 UUID ,100年後只產生一次重複的機率是50%。如果地球上每個人都各有6億筆 UUID,發生一次重複的機率是50%。與被隕石擊中的機率比較的話,已知一個人每年被隕石擊中的機率估計爲170億分之1,也就是說機率大約是0.00000000006 (6 x 10^{-11}),等同於在一年內生產2000億個 UUID 併發生一次重複。

Java實現

Java中 UUID 對版本4進行了實現,原理是由強僞隨機數生成器生成僞隨機數。

    /**
     * 使用靜態工廠來獲取版本4(僞隨機數生成器)的 UUID
     * Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
     * 這個UUID生成使用了強加密的僞隨機數生成器(PRNG)
     * The {@code UUID} is generated using a cryptographically strong pseudo
     * random number generator.
     *
     * @return  A randomly generated {@code UUID}
     */
    public static UUID randomUUID() {
        // 與Random(弱僞隨機數生成器)不一樣,SecureRandom是強僞隨機數生成器,結果不可預測
        // 使用SecureRandom生成隨機數,替換version和variant就是 UUID
        SecureRandom ng = Holder.numberGenerator;

        byte[] randomBytes = new byte[16];
        ng.nextBytes(randomBytes);
        randomBytes[6]  &= 0x0f;  /* clear version        */
        randomBytes[6]  |= 0x40;  /* set to version 4     */
        randomBytes[8]  &= 0x3f;  /* clear variant        */
        randomBytes[8]  |= 0x80;  /* set to IETF variant  */
        return new UUID(randomBytes);
    }

    /**
     * 對版本3的實現,對於給定的字符串(name)總能生成相同的UUID
     * Static factory to retrieve a type 3 (name based) {@code UUID} based on
     * the specified byte array.
     *
     * @param  name
     *         A byte array to be used to construct a {@code UUID}
     *
     * @return  A {@code UUID} generated from the specified array
     */
    public static UUID nameUUIDFromBytes(byte[] name) {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsae) {
            throw new InternalError("MD5 not supported", nsae);
        }
        byte[] md5Bytes = md.digest(name);
        md5Bytes[6]  &= 0x0f;  /* clear version        */
        md5Bytes[6]  |= 0x30;  /* set to version 3     */
        md5Bytes[8]  &= 0x3f;  /* clear variant        */
        md5Bytes[8]  |= 0x80;  /* set to IETF variant  */
        return new UUID(md5Bytes);
    }

生成 UUID

// Java語言實現
import java.util.UUID;

public class UUIDProvider{
    public static String getUUID(){
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    public static void main(String[] args) {
        // 利用僞隨機數生成版本爲4,變體爲9的UUID
        System.out.println(UUID.randomUUID());
        
        // 對於相同的命名空間總是生成相同的UUID,版本爲3,變體爲9
        // 命名空間爲"mwq"時生成的UUID總是爲06523e4a-9a66-3687-9334-e41dab27cef4
        System.out.println(UUID.nameUUIDFromBytes("mwq".getBytes()));
    }
} 

總結

使用較多的是版本1和版本4,其中版本1使用當前時間戳和MAC地址信息。版本4使用(僞)隨機數信息,128bit中,除去版本確定的4bit和variant確定的2bit,其它122bit全部由(僞)隨機數信息確定。

因爲時間戳和隨機數的唯一性,版本1和版本4總是生成唯一的標識符。若希望對給定的一個字符串總是能生成相同的 UUID,使用版本3或版本5。

  • 如果只是需要生成一個唯一ID,你可以使用V1或V4。

    - V1基於時間戳和Mac地址,這些ID有一定的規律(你給出一個,是有可能被猜出來下一個是多少的),而且會暴露你的Mac地址。
    - V4是完全隨機(僞)的。


  • 如果對於相同的參數需要輸出相同的UUID,你可以使用V3或V5。

    - V3基於MD5 hash算法,如果需要考慮與其它系統的兼容性的話,就用它,因爲它出來得早,大概率大家都是用它的。
    - V5基於SHA-1 hash算法,這個是首選。



參考

【1】什麼是UUID

【2】uuid如何保證一致性

【3】https://www.ietf.org/rfc/rfc4122.txt

【4】https://www.zhihu.com/question/34876910/answer/88924223

【5】https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding

擴展閱讀

SecureRandom 版本4的UUID生成原理,與Random的區別等
https://resources.infosecinstitute.com/random-number-generation-java/
https://www.saowen.com/a/d2f3430d15558a740d95b76ff04b242d59b08ea44847ff5941a9ded0e08a65c1
http://yizhenn.iteye.com/blog/2306293
mysql UUID
https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid
Leaf——美團點評分佈式ID生成系統





 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章