浅谈 UUID 生成原理及优缺点

2,411 阅读8分钟

UUID 是一套用于生成全局唯一标识符的标准,也被称为 GUID (Globally Unique Identifier),通过使用 UUID 可以在分布式系统中生成唯一的 ID。UUID 的生成方式有多种,本文将详细讲解 UUID 的生成原理、特性、实用场景以及优缺点。

一、UUID 的生成原理

UUID 的英文全称为 Universally Unique Identifier,即通用唯一识别码,它是由一组 16 个字节(128 位)组成的标识符,可以用于唯一地标识信息。UUID 的生成方式有多种,其中最为常用的是基于算法的 UUID 生成方式和基于硬件的 UUID 生成方式。下面我们分别来介绍这两种生成方式的原理。

1. 基于算法的 UUID 生成方式

基于算法的 UUID 生成方式是指使用计算机程序根据一定的算法生成 UUID。这种方式的优点是可以在任何环境中生成 UUID,并且不需要依赖于任何硬件设备,缺点是生成的 UUID 不够随机,容易被猜测到,因此不适合用于安全领域。

目前最常用的基于算法的 UUID 生成方式是基于时间戳的 UUID 生成方式。该方式基于当前时间戳和机器的 MAC 地址生成 UUID,它的算法流程如下:

  1. 获取当前时间戳和机器的 MAC 地址;
  2. 将当前时间戳转换为 UTC 时间,并计算出自 1582 年 10 月 15 日午夜(即格林威治标准时间 0 点)以来的纳秒数,将其存储在 UUID 的时间戳字段中;
  3. 将机器的 MAC 地址哈希得到其中的 6 个字节作为 UUID 的节点字段;
  4. 随机生成两个字节作为 UUID 的时钟序列字段;
  5. 将时间戳、节点、时钟序列等信息组合起来,生成 UUID。

基于时间戳的 UUID 生成方式可以保证在同一时刻生成的 UUID 唯一,并且可以提供一定的顺序性,因此非常适合用于分布式系统中对数据进行排序操作。但是,如果多台计算机的时钟存在差异,就有可能导致生成重复的 UUID,因此需要采取一些措施来防止时钟不同步的问题。此外,这种方式也容易受到时钟回拨攻击的影响,因此需要特殊处理。

2. 基于硬件的 UUID 生成方式

基于硬件的 UUID 生成方式是指使用计算机硬件设备生成 UUID,该方式的优点是生成的 UUID 随机性高,不容易被猜测到,缺点是只能在具备对应硬件设备的机器上生成 UUID,不够灵活。

基于硬件的 UUID 生成方式常用的有 MAC 地址 UUID 和 CPU ID UUID。

MAC 地址 UUID 是指使用计算机网卡设备的 MAC 地址作为 UUID 的节点字段,这种方式可以保证每个机器生成的 UUID 都是唯一的,并且不依赖于时钟同步。但是,它也存在一些缺点,比如计算机的网卡可能会被更换,导致 UUID 发生变化。

CPU ID UUID 是指使用计算机 CPU 的序列号作为 UUID 的节点字段,这种方式与 MAC 地址 UUID 很相似,但是比 MAC 地址 UUID 更加安全,因为 CPU 序列号不会轻易发生变化。不过,由于 CPU 可以被替换,因此这种方式也存在一定的风险。

二、UUID 的特性

UUID 具有以下几个特性:

1. 唯一性

UUID 是全局唯一的标识符,可以为分布式系统提供唯一的标识。

2. 随机性

UUID 的生成过程使用了随机性或伪随机性的元素,生成的 UUID 具有高度随机性,不容易被猜测到。

3. 不可推测性

UUID 是通过一定的算法生成的,生成的 UUID 不能从中推测出任何信息。

4. 可复制性

UUID 可以在不同的时间和地点被重复生成,但是在实践中,由于随机数的使用,重复的概率非常低。

5. 可比较性

UUID 是一个 128 位的二进制数字,可以进行比较操作,比较操作具有一定的顺序性。

三、UUID 的实用场景

由于 UUID 具有唯一性、随机性等特性,因此在很多应用场景中都得到了广泛的应用,下面列举几个典型的应用场景。

1. 数据库主键

在数据库中,每个记录都需要一个唯一的主键来区分,通常可以使用自增长 ID 作为主键。但是在分布式系统中,多个节点之间可能会产生 ID 冲突的问题。因此,可以使用 UUID 作为主键,确保每个记录的唯一性。

2. 分布式系统

在分布式系统中,需要将数据分布存储在多个节点上,并对数据进行全局唯一标识。这时可以使用 UUID 来为数据生成唯一的 ID,以便在整个系统中进行区分。

3. 日志跟踪

在分布式系统中,系统运行日志是监控和排查问题的重要依据。如果将日志中的每条记录都打上唯一的标识符,方便后期分析和跟踪。这时可以使用 UUID 来为日志记录生成唯一的 ID。

4. 安全领域

在安全领域中,需要为用户会话、密钥和证书等生成唯一的标识符,以保证安全性。这时可以使用 UUID 来生成唯一的标识符,确保不同对象之间的区分。

四、UUID 的优缺点

UUID 具有以下几个优点:

1. 全局唯一

UUID 可以为分布式系统提供全局唯一的标识符,避免了 ID 冲突的问题。

2. 可比较性

UUID 是一个数字,可以进行比较操作,具有一定的顺序性。

3. 无需中央协调机制

UUID 的生成不需要中央协调机制,因此可以在任何时间和地点生成 UUID。

4. 安全性高

UUID 的生成使用了随机性或伪随机性的元素,生成的 UUID 具有高度随机性,不容易被猜测到。因此可以在安全领域中使用。

但是,UUID 也具有以下几个缺点:

1. 占用空间大

UUID 是一个 128 位的二进制数字,占用的空间比较大,不适宜作为数据库主键使用。

2. 不易读懂

由于 UUID 是一个数字,因此不容易被人类读懂,不便于调试和排查问题。

3. 不适合顺序访问

UUID 的生成具有随机性,因此不适合对数据进行顺序访问操作。

4. 算法复杂

UUID 的生成算法比较复杂,在一定程度上影响了性能。

五、代码示例

import java.util.Random;

public class GenerateUUID {
    private static final long START_EPOCH = -12219292800000L;
    // 定义版本号和变体标识的位数
    private static final int VERSION_BITS = 4;
    private static final int VARIANT_BITS = 2;
    // 生成的 UUID 二进制形式共 128 位
    private static final int TOTAL_BITS = 128;

    public static void main(String[] args) {
        Random random = new Random();
        long timestamp = System.currentTimeMillis() + START_EPOCH;
        long leastSigBits = random.nextLong();
        long mostSigBits = random.nextLong();

        // 根据 RFC 4122 规范设置 UUID 版本号和变体标识
        mostSigBits &= ~(0xfL << 12); // 清空版本号
        mostSigBits |= (4L << 12); // 设置版本号
        leastSigBits &= ~(0x3L << 62); // 清空变体标识
        leastSigBits |= (0x2L << 62); // 设置变体标识

        // 将时间戳写入 UUID 中段
        long timeLow = timestamp & 0xffffffffL;
        long timeMid = (timestamp >> 32) & 0xffffL;
        long timeHiAndVersion = (timestamp >> 48) & 0x0fffL;
        mostSigBits &= ~(0xffffffffffffL << 64); // 清空时间戳
        mostSigBits |= (timeLow << 32);
        mostSigBits |= (timeMid << 16);
        mostSigBits |= timeHiAndVersion;

        // 构造 UUID 对象
        java.util.UUID uuid = new java.util.UUID(mostSigBits, leastSigBits);
        System.out.println(uuid.toString());
    }
}

上述代码中,我们首先使用 Random 类生成两个 64 位的伪随机数,分别作为 UUID 的高 64 位和低 64 位。然后,我们根据 RFC 4122 规范对 UUID 进行版本号标记和变体标识,并将当前时间戳的毫秒部分写入 UUID 的中段,以保证 UUID 具有一定的顺序性。最后,我们将生成的 UUID 的高 64 位和低 64 位传入 java.util.UUID 类的构造方法中,以创建一个 UUID 对象。

需要注意的是,由于我们在生成 UUID 的过程中使用了伪随机数,因此生成的 UUID 并不是真正的随机数,它们仅具有伪随机性。同时,由于我们没有保证时间戳的唯一性,因此同一时刻生成的 UUID 可能存在重复的风险。在实际应用中,我们通常建议使用 java.util.UUID.randomUUID() 方法生成 UUID。

六、总结

UUID 是一套用于生成全局唯一标识符的标准,具有唯一性、随机性等特性,可以在分布式系统中使用。UUID 的生成方式有多种,其中最为常用的是基于算法的 UUID 生成方式和基于硬件的 UUID 生成方式。UUID 在数据库主键、分布式系统、日志跟踪和安全领域等方面有广泛的应用。虽然 UUID 具有很多优点,但也存在一些缺点,比如占用空间大、不易读懂、不适合顺序访问和算法复杂等。综合来看,UUID 是一种非常有用的标识符生成方式,在实际开发中应根据具体情况选择合适的 UUID 生成方式。