UUID在Java中的實現與應用


UUID是什么

UUID的全稱為:Universally Unique IDentifier,也被稱為GUID(Globally Unique IDentifier)。是一種由算法生成的唯一標識,它實質上是一個128位長的二進制整數。通常表示成32個16進制數組成的字符串,如:21EC2020-3AEA-1069-A2DD-08002B30309D。關於UUID標准的rfc定義詳見:http://www.ietf.org/rfc/rfc4122.txt。 當然,GUID一詞有時也專指微軟對UUID標准的實現,用於Windows操作系統中。

UUID的實現

UUID的格式是這樣的:xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,一共為32個16進制數。
M那個位置,代表版本號,目前UUID的標准實現有5個版本,所以只會是1,2,3,4,5
N那個位置,只會是8,9,a,b

UUID的具體實現存在多個版本,分別為:

1. 基於時間的UUID

基於時間的UUID通過計算當前時間戳、隨機數和機器MAC地址得到。由於在算法中使用了MAC地址,這個版本的UUID可以保證在全球范圍的唯一性。但與此同時,使用MAC地址會帶來安全性問題,這就是這個版本UUID受到批評的地方。如果應用只是在局域網中使用,也可以使用退化的算法,以IP地址來代替MAC地址。

2. DCE(Distributed Computing Environment)安全的UUID

和基於時間的UUID算法相同,但會把時間戳的前4位置換為POSIX的UID或GID,這個版本的UUID在實際中較少用到。

3. 基於名稱空間的UUID(MD5)

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

4. 基於隨機數的UUID

根據隨機數,或者偽隨機數生成UUID。這種UUID產生重復的概率是可以計算出來的,但隨機的東西就像是買彩票:你指望它發財是不可能的,但狗屎運通常會在不經意中到來。可能在測試的時候多線程並發也不見得出現重復,但是卻不能保證系統正式上線之后不會出現不重復的UUID,特別是在分布式系統中。

5. 基於名稱空間的UUID(SHA1)

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

在Java中默認實現了基於名稱空間的UUID(UUID Version 3)和基於偽隨機數的UUID(UUID Version 4),分別為:

/**
 * 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);
}
/**
 * Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
 *
 * The {@code UUID} is generated using a cryptographically strong pseudo
 * random number generator.
 *
 * @return  A randomly generated {@code UUID}
 */
public static UUID randomUUID() {
    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);
}

除了Java默認的實現之外,還有一個開源的UUID實現庫可以參考:https://github.com/cowtowncoder/java-uuid-generator, 這個庫支持實現UUID的V1,V3,V4和V5版本,在需要使用到唯一性ID的地方可以酌情考慮使用。

關於UUID使用的思考和總結

UUID是為了解決標識唯一性而提出的,這在分布式應用場景下非常常見。例如,用戶登錄Token,數據庫記錄主鍵ID等等。但是對於是否可以使用UUID(除了考慮唯一性之外,可能還要考慮有序性),以及使用哪個版本的UUID實現(考慮到效率等因素)需要慎重。例如:雖然UUID可以解決唯一性,但是卻不適合直接用於數據庫記錄主鍵ID,對於數據庫主鍵ID而言,除了考慮唯一性之外,還要考慮有序性,索引效率等因素。而在用戶登錄Token標識這種場景下使用UUID是可以的,甚至在使用手機或郵箱作為唯一名稱標識的場景下,可以使用基於名稱空間的UUID。
通常來講,如果僅僅需要實現唯一性需求,那么對於使用UUID有如下建議:

  • 對於暴露MAC地址不敏感的場合,使用UUID V1是最佳選擇。當然了,也可以通過對UUID進行MD5散列的方式進行保密,不過這需要考慮性能開銷。
  • 如果可以保證在指定命名空間內的名稱唯一性,例如手機號或者郵箱,那么選擇UUID V3或者V5的實現也能保證唯一性。
  • 對於UUID V4,如果是基於偽隨機數的實現,是存在出現重復UUID的概率的,如果對於ID唯一性要求不是十分嚴格的場景,這個版本的實現也可以考慮。

另外,在各個語言平台對應UUID實現的支持各不相同。
1.Java語言
默認只支持V3和V4(基於偽隨機數)兩種版本的實現

2.Python語言
支持V1,V3,V4,V5版本的UUID實現
Python的UUID V1基於時間戳和MAC地址,最后12個16進制字符就是網卡地址。

>>> import uuid
>>> uuid.uuid1()
UUID('d3a173de-0ca9-11e8-af24-f0d5bf9aedc1')
>>> uuid.uuid1()
UUID('73e4ac9e-0caa-11e8-aa82-f0d5bf9aedc1')

Python支持UUID V3實現,對名稱空間內的字符串進行MD5散列值生成UUID。

>>> uuid.uuid3(uuid.NAMESPACE_DNS,"chench")
UUID('a0fda26d-acf1-37da-ad64-7cac7753de92')
>>> uuid.uuid3(uuid.NAMESPACE_DNS,"chench")
UUID('a0fda26d-acf1-37da-ad64-7cac7753de92')

Python的UUID V4實現基於偽隨機數實現,這種UUID產生重復的概率是可以計算出來的。

>>> uuid.uuid4()
UUID('d1437e20-95eb-446a-b9ca-9184013b8542')
>>> uuid.uuid4()
UUID('4dce6000-0ad5-4f35-84d8-6b434205d212')

與UUID V3的算法一致,不同的是UUID V5的散列算法為SHA1。

>>> uuid.uuid5(uuid.NAMESPACE_DNS,"chench")
UUID('b8643db9-49d8-5a98-842d-14f3215f08cb')
>>> uuid.uuid5(uuid.NAMESPACE_DNS,"chench")
UUID('b8643db9-49d8-5a98-842d-14f3215f08cb')

【參考】
https://zh.wikipedia.org/wiki/通用唯一識別碼 UUID
https://zh.wikipedia.org/wiki/全局唯一標識符 GUID
https://www.jianshu.com/p/d77f3ef0868a 關於UUID的二三事
https://qtdebug.com/java-duplicate-uuid/ 測試 Java 生成 UUID 是否重復
http://www.infoq.com/cn/articles/talk-about-the-history-of-uuid UUID簡史


免責聲明!

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



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