1.UUID是什么?
UUID 是指(Universally Unique Identifier)通用唯一识别码,128位。RFC 4122描述了具体的规范实现。
2.UUID解决问题?
分库分表后自增Id重复问题。
3.UUID作用?
UUID 的目的是让分布式系统中的所有元素都能有唯一的识别信息。
4.UUID定义
UUID使用16进制表示,共有36个字符(32个字母数字+4个链接符“-”)组成,格式为8-4-4-4-12;是128位。
 是由一组32位数的16进制数字所构成,故UUID理论上的总数为16^32 ;
格式:
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。
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。
版本1——date-time & MAC address
 基于时间戳及MAC地址的UUID实现。它包括了48位的MAC地址和60位的时间戳。
 版本2——date-time Mac address
 RFC没有提供具体的实现细节,以至于大部分的UUID库都没有实现它,只在特定的场景(DCE security)才会用到。所以绝大数情况,我们也不会碰到它。
 版本3,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都是一样的。
 具体的流程就是
- 把namespace和输入参数拼接在一起,如"http/http://wwwbaidu.com" ++ “/query=uuid”;
- 使用MD5算法对拼接后的字串进行hash,截断为128位;
- 把UUID的Version和variant字段都替换成固定的;
- 如果需要to_string,需要转为16进制和加上连接符"-"。
 把V3的hash算法由MD5换成SHA-1就成了V5。
 版本4——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)还要大很多倍。
5.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 ),等同于在一年内生产2000亿个 UUID 并发生一次重复。
6.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()));
    }
} 
7.总结:
使用较多的是版本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算法,这个是首选。 
 参考
 【0】原文
 【1】什么是UUID
 【2】uuid如何保证一致性
 【3】RFC规范
扩展阅读
 MySQL 的 UUID
 Leaf——美团点评分布式ID生成系统
 
![[数据库系统] 一、创建表以及使用主键约束(educoder)](https://img-blog.csdnimg.cn/7a5e570d1dd1437d85d113eebe460cfa.png)

















