FlowingDev

UUID 详解:一种绝不会『撞车』的唯一 ID

UUID 是一个 128 位的数字,用于在计算机系统中唯一地标识信息,它能近乎百分百地保证,绝不会生成两个一模一样的出来。

试用工具: UUID生成器

一句话概括

UUID 是一个 128 位的数字,它就像一个唯一的序列号,可以用于软件中你能想到的任何东西,而且两次创建出相同 UUID 的概率低到离谱。

它解决的问题

在计算机的早期,追踪记录信息还挺简单的。你的第一个用户 ID 是 1,第二个是 2,以此类推。这种“自增整数”的方案运行良好……前提是你只有一个数据库和一台服务器来创建所有记录。

然后,互联网来了。分布式系统来了。微服务来了。离线优先的应用也来了。

突然之间,多台计算机都需要在互不通信的情况下,同时创建新东西(比如用户、帖子、商品、日志条目)。如果都柏林的服务器和东京的服务器都想创建“下一个”记录,它们可能都会创建出 #5830 号记录。当它们的数据库稍后同步时,就会发生冲突(collision)。哪个才是真正的 #5830 号记录?场面一度陷入混乱。

这就是 UUID(通用唯一识别码,Universally Unique Identifiers)解决的核心问题:去中心化、无须协调、生成唯一 ID。 一个在咖啡馆里用笔记本电脑的开发者可以为一个新的待办事项创建 ID,并且在统计学上可以确信,在整个宇宙的过去和未来,任何其他人在任何其他计算机上,都永远不会生成完全相同的 ID。这使得系统可以独立地创建唯一标识符,为我们今天所依赖的强大、分布式的软件铺平了道路。

底层工作原理

说白了,UUID 就是一个超大的数字:128 位长。也就是 2¹²⁸ 种可能的组合,大约是 340澗(一个 3 后面跟 37 个零)。给你个直观的感受:就算你每秒生成十亿个 UUID,也需要大约 100 亿年才能用完所有可能性。两个随机生成的 UUID 发生冲突的概率小到可以忽略不计。

UUID 的解剖学

虽然它是一个 128 位的整数,但我们几乎从不以这种形式看到它。它几乎总是被表示为一个 32 个字符的十六进制字符串,并用连字符分成五组。

一个典型的 UUID(版本 4)长这样:123e4567-e89b-42d3-a456-426614174000

我们来拆解一下这个格式:

  • 结构: 8-4-4-4-12(代表 32 个十六进制字符,加上连字符共 36 个字符)。
  • 数据: 每个十六进制字符代表 4 个 bit(一个“半字节”或“nibble”)。32 个字符 * 4 bit/字符 = 128 bit。
  • 神奇的数字: 看到第三组(42d3)开头的那个 4 了吗?那个 4 不是随机的。它指定了 UUID 的版本(这里是版本 4)。第四组(a456)的第一个字符也有特殊含义;它标识了变体(variant),确保其符合标准布局。对于你将见到的大多数 UUID,这个位置会是 8、9、A 或 B 中的一个。

各版本巡礼

本工具提示的是版本 4(v4),这是最常见的类型。但其实 UUID 有好几个版本,每个版本都有不同的生成策略。

版本 生成方法 使用场景
v1 时间戳 + 生成计算机的 MAC 地址。 当你需要基于时间的排序时。(现在很少使用,因为暴露 MAC 地址存在隐私问题)。
v2 与 v1 相同,但增加了 POSIX UID/GID 信息。 极其罕见。是对 v1 的一种形式化。
v3 一个“命名空间”和一个“名称”的 MD5 hash 值。 确定性的。给定相同的命名空间和名称,你总能得到相同的 UUID。(不太常用,因为 MD5 有已知的弱点)。
v4 纯随机。 默认选择。当你只是需要一个唯一 ID,而不在乎其他任何事情时。
v5 一个“命名空间”和一个“名称”的 SHA-1 hash 值。 现代的确定性选择。和 v3 的思路一样,但用了更强的 hash 函数。

生成一个版本 4 UUID

生成一个 v4 UUID 在概念上很简单:

  1. 生成 128 位的加密学安全(cryptographically strong)的随机数据。
  2. 按照标准要求,微调几个特定的 bit 位,以设置“版本”和“变体”字段。
  3. 将得到的 128 bit 格式化为带连字符的十六进制字符串。

下面是“微调”步骤的伪代码:

// Assuming `bits` is an array of 128 random bits (0s and 1s)

// Set the version to 4 (0100)
bits[48] = 0;
bits[49] = 1;
bits[50] = 0;
bits[51] = 0;

// Set the variant to '10x'
bits[64] = 1;
bits[65] = 0;

在现实中,大多数编程语言都提供了一行代码的函数,比如 crypto.randomUUID(),来帮你完成所有这些工作,确保操作正确且安全。关键在于,一个 v4 UUID 其实就是 122 位的纯随机数据,外面包了 6 位的元数据。

真实世界的故事

数据库合并噩梦

两家初创公司“Acme”和“WidgetCorp”决定合并。两家都有成功的产品,也都有各自的用户、产品和订单数据库。在第一次整合会议上,一个初级开发问:“我们怎么合并用户表?我的 ID 为 101 的用户是‘Alice’,但他们的 ID 为 101 的用户是‘Bob’。” 整个会议室瞬间安静了。两个数据库里的每一张表都用的是简单的自增整数 ID。要合并它们将是一项巨大的工程,需要重写外键、交叉引用每一条记录,并祈祷不要漏掉任何东西。这个难题让他们的合并计划推迟了数月。

**教训:**如果他们从一开始就使用 UUID,合并过程就会非常简单。来自 Acme 的用户 f47ac10b-58cc-4372-a567-0e02b2c3d479 可以与来自 WidgetCorp 的用户 9c68a520-2a83-43a3-b45d-4c86518a28cc 完美共存。没有冲突,没有噩梦。对于那些未来可能需要交互或合并的系统来说,UUID 是必不可少的。

反应飞快的购物车

一位开发者正在开发一个新的电商“快速添加”功能。当用户在商品列表上点击“添加到购物车”时,会显示一个加载动画,持续 1-2 秒,等待服务器创建购物车项目并返回其新 ID。这感觉有点卡顿。这位开发者灵光一闪:如果 App 不用等呢?她修改了代码,当用户点击时,浏览器立即为新的购物车项目生成一个 v4 UUID,将其添加到本地状态,并瞬间更新界面。App 感觉快如闪电。在后台,它向服务器发送请求,说:“请用这个特定的 UUID 创建一个购物车项目。” 如果网络请求失败,App 稍后可以重试,使用相同的 UUID 来避免创建重复的项目。

**教训:**客户端生成 UUID 可以实现“乐观 UI”(Optimistic UI),即界面立即更新,并假设操作会成功。这能创造出更快、响应更灵敏的用户体验,并使处理离线场景变得更加简单。

微服务探案记

一位顾客报告了一个错误:他的订单失败了,但信用卡却被扣了款。这个系统是一个由 Auth、Gateway、Orders、Payments、Shipping 等微服务组成的复杂网络。一个请求可能会在五六个服务之间跳转。在每分钟数百万条的日志中找到故障的确切点,就像大海捞针。首席架构师强制推行了一项改动:每一个进入网关(Gateway)的请求都会被分配一个 UUID,称为“关联 ID”(Correlation ID)。这个 ID 会被传递给处理该请求的每一个微服务,并且每一条日志信息都必须包含它。下一次发生错误时,支持团队只需在日志系统中搜索那个唯一的 UUID。瞬间,他们就得到了这个请求在整个系统中流转的完整、按时间顺序排列的故事,并精确定位了出故障的服务。

**教训:**在分布式和微服务架构中,UUID 作为关联 ID(correlation ID)对于追踪请求和调试来说,非常有价值。

常见错误和陷阱

  • 漫不经心地使用 UUID 作为数据库主键…… 虽然在唯一性方面表现出色,但 UUID 很大(16 字节,而整数是 4 或 8 字节)并且是随机的。随机性对数据库索引性能可能是灾难性的,因为它会导致索引碎片化和写入速度变慢,因为数据库需要费力地将新行插入到索引 B-tree 的中间位置。现代数据库和更新的 UUID 版本(比如提案中的 v7,它是按时间排序的)可以缓解这个问题,但这是一个需要注意的关键权衡。
  • 想当然地认为所有 UUID 都是随机的。 开发人员可能会在旧系统中看到一个 UUID,并基于它是不可预测的假设来构建逻辑。他们可能没意识到这是一个 v1 UUID,其中包含了时间戳和生成它的机器的 MAC 地址,这可能会泄露敏感信息。
  • 把它当作普通字符串对待。 有些开发者可能认为任何唯一的字符串都是“UUID”。他们可能会用 "product-123" 这样的字符串,或者用一个弱随机数生成器来创建 ID。真正的 UUID 遵循严格的格式,并且对于 v4 来说,应该使用加密学安全的随机源来生成,以保证唯一性。
  • 用错了版本。 一个常见的错误是在需要确定性 ID 时使用了 v4(随机)UUID。例如,如果你需要根据文件内容为文件生成一个唯一 ID,你应该使用 v5 UUID,并将文件的 hash 值作为“名称”。这能确保如果你再次遇到相同的文件,你会生成完全相同的 UUID,从而轻松实现去重。

为什么你应该关注它

当你遇到以下情况时,就应该考虑使用 UUID 生成器:

  • 你需要创建一个唯一标识符,但又不能依赖于一个中央权威(比如单一的数据库序列)。
  • 你正在构建一个分布式系统、微服务,或任何需要多个实例独立创建数据的应用。
  • 你希望在客户端(浏览器或移动应用)生成唯一 ID,以实现乐观 UI 更新或离线功能。
  • 你需要创建关联 ID(correlation ID)来追踪跨多个系统的请求流。
  • 你正在为数据库表选择主键,并且你更看重全局唯一性,而不是原始的插入性能(并且你已经考虑过其中的权衡)。

在现代软件开发中,这些场景是常态,而非例外。知道何时以及如何使用 UUID 是一项基本技能。

深入了解

理论搞定,动手试试吧——100% 在你的浏览器中运行。

试用工具: UUID生成器