前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >为什么UUID不适合作为分布式全局唯一ID?

为什么UUID不适合作为分布式全局唯一ID?

原创
作者头像
写bug的高哈哈
发布2025-01-20 16:56:59
发布2025-01-20 16:56:59
960
举报

在单一系统的时期,应用程序通常部署在单一的物理服务器上,数据则存储于单一的数据库之中,我们能够利用数据库的递增 ID 来确保 ID 全局唯一。

随着系统架构向分布式系统的转变,以及业务和数据量的激增,我们开始采用分库分表的策略。在这种复杂的分布式环境中,对数据进行唯一标识的需求日益增长,而传统的数据库递增 ID 已不再适用。因此,我们需要探索其他方法来实现全局唯一的 ID。

在先前的文章中,我介绍了如何利用号段模式和雪花算法来创建分布式 ID。今天,我将探讨如何使用 UUID 来实现分布式 ID,并分析为何 UUID 不适宜作为分布式系统中的全局唯一 ID。

什么是 UUID

首先,让我们简要了解 UUID 的概念。UUID,即通用唯一识别码,是一种计算机生成的、基于当前时间、计数器和硬件标识符等数据的全局唯一标识符。标准的 UUID 由 36 个字符组成,格式为 5 组字符串通过中划线组成,头部有 1 组 8 字节的字符串,尾部有 1 组 12 字节的字符串,中间分别有 3 组 4 字节的字符串。

UUID 有多个版本,每个版本的生成算法都有所不同,大致可以分为四类:基于时间的、DCE 安全的、基于名称的和随机生成的。接下来我们一一展开讲解。

基于时间的 UUID 版本

第一版的 UUID 算法以时间为基准。这种算法相对简单,它利用当前的时间戳和网络接口的 MAC 地址来生成 UUID。这个版本的 UUID 算法保证了生成的 UUID 是全局唯一的,但由于这个算法基于时间戳和 MAC 地址生成 UUID,在同一台机器上同时运行多个进程,有可能在同一微秒内生成的多个 UUID 会发生冲突。基于这点考虑,我们通常不会使用第一个版本的 UUID 算法。

DCE 安全的 UUID

第二个版本的 UUID 算法是 DCE 安全的 UUID。之所以叫做 DCE 安全的 UUID,是因为它们在 1990 年代初期,被定义为分布式计算环境(DCE)“身份验证和安全服务”的一部分。

第二个版本的 UUID 相对于上一个版本的算法更加安全,但它没有被广泛使用,是因为业内认为它识别本地标识符可能会侵犯隐私,另外是有损时间戳会导致精度损失。

基于名字的 UUID(MD5)

第三个版本的 UUID 算法是基于名字的算法。它使用 MD5 作为哈希算法来组合命名空间和名称,该算法确保了不同命名空间中的 ID 值的唯一性,但是,我们需要关注的是,在相同命名空间中,有可能生成的多个 UUID 会发生冲突。

随机生成的 UUID

第四个版本的 UUID 算法是基于随机数的算法。在业内,该版本可能是最常用的 UUID 版本。因为它们是随机生成的,不涉及创建时间和生成它们的机器的信息。该版本生成的 UUID 也是有可能出现 ID 重复问题的,只是 ID 重复问题出现的概率非常小。

基于名字的 UUID(SH1)

如果不考虑向后兼容性,优先考虑版本 5 的 UUID 算法。第五个版本的 UUID 算法也是基于名字的算法。不同的是,这个版本的 UUID 算法使用 SHA1 作为哈希算法来组合命名空间和名称。

小结

我简单总结一下,UUID 是有 5 个版本,第一个版本是基于时间算法,多个 UUID 值有可能在同一微秒内出现重复。第二个版本是基于分布式计算环境安全的算法,但是,该算法在所有的 UUID 实现中都不建议使用。

有两个版本的 UUID 算法都是基于名字的算法,不同在于,生成 UUID 的散列算法不一样。最后一个版本的 UUID 算法是基于随机数的算法,是我们最通用的方案,因为随机生成的 UUID,不涉及创建时间和机器信息,同时也能最大程度地保证全局唯一。

Java 的 java.util.UUID 类库中,对 UUID 版本 3 基于名字的 UUID 和 UUID 版本 4 随机生成的 UUID 提供了支持,分别提供了 nameUUIDFromBytes()和 randomUUID()两个方法。

代码语言:java
复制
public static UUID nameUUIDFromBytes(byte[] name);
public static UUID randomUUID();

UUID 不依赖于数据库和外部其他服务,我们可以直接利用业务服务本地的计算资源,在业务服务中调用 randomUUID()方法,实时计算并生成一个可用的 UUID 作为业务 ID。

为什么 UUID 不适合作为分布式全局唯一 ID

既然 UUID 这么好用,在确保稳定性的同时,性能也非常好,难能可贵的是,UUID 具有不规则性,也确保了业务信息的安全性。

那你是不是很疑惑,为什么我说 UUID 不适合作为分布式全局唯一 ID 呢?

因为 UUID 有利也有弊,在实际使用的时候,弊端影响更大。

对于数据库主键 ID,我们不仅要考虑全局唯一性和信息安全,还要考虑有序性,以及数据库的索引效率等因素。5 个版本的 UUID 生成都无法满足有序性,包含基于时间的 UUID 也不是。现在,我们来重点关注下数据库的索引效率。

在 InnoDB 引擎中,MySQL 数据库的索引是以 B+树来实现的。而且,MySQL 数据库的主键使用聚簇索引。

什么是聚簇索引?聚簇索引的数据文件本身就是索引文件,按照表的主键组织成一颗 B+树,同时,这棵树的叶子节点保存了完整的数据记录。

MySQL 数据库的查询会涉到磁盘 IO,并且,磁盘 IO 相比于内存 IO 的消耗要高好几个数量级,为了提升效率,MySQL 数据库采取预读机制,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。

假设,我们有一个主键列为 ID 的表,表中的 ID 分别为 5、9、11、15。这棵树简化后,大概是这样的。这棵树的叶子节点保存了完整的数据记录。

MySQL 数据库为了优化聚簇索引的查询,在新增数据的时候需要维护 B+树的有序性。因此,如果 ID 是趋势递增的。那么,MySQL 数据库可以保证新增数据的 ID 一定会在叶子节点最右边,并且,不会影响原先已经存在的数据。

假设,我们需要新增一条数据,而这条数据的自增 ID 是 19。那么,ID 为 19 的这条数据会在叶子节点最右边。

但是,如果新增数据的 ID 是无序的,那么,每次新增数据的时候都会调整这棵树的数据,会导致数据的移动。如果正好这个新增记录所在的数据页已经满了,那么,数据库就需要申请一个新的数据页来进行数据的存储,这个时候,为了维护 B+树的有序性,就需要移动部分数据到新的数据页。这个过程,就是页分裂。

UUID 无序性会导致频繁的页分裂。然而,页分裂是 MySQL 数据库为了维护 B+树的有序性的机制,这个机制确保了后一个数据页中的所有的 ID 值一定比前一个数据页中的 ID 值大。

所以,UUID 无序性导致的页分裂会影响到数据库的写入性能。同时,页分裂还会影响数据页的空间利用率,不适合作为分布式全局式唯一 ID。

总结

今天,我们围绕 UUID 模式实现分布式 ID 进行了讨论。UUID 是有五种版本的,每个 UUID 的版本的算法是不同的。我们最常用的是第 4 版本,通过伪随机数计算生成的 36 字节的 UUID 字符串。

UUID 不依赖于数据库和外部其他服务,可以直接在本地服务实时计算并生成一个可用的 UUID 作为业务 ID。同时,UUID 满足全局唯一性和安全性。

但是,MySQL 数据库为了维护 B+树的有序性,在面对主键 ID 插入数据库时的无序性,MySQL 数据库会采取页分裂机制。频繁的页分裂,一方面会影响到数据库的写入性能,另一方面也会影响数据页的空间利用率。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 UUID
    • 基于时间的 UUID 版本
    • DCE 安全的 UUID
    • 基于名字的 UUID(MD5)
    • 随机生成的 UUID
    • 基于名字的 UUID(SH1)
    • 小结
  • 为什么 UUID 不适合作为分布式全局唯一 ID
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档