需要一个不会与其他人创建的标识符发生冲突,在一个您从未听说过的机器上,且无需向任何人请求许可的标识符吗?这就是 UUID 的作用。它是一个 128 位数字,通常以熟悉的 8-4-4-4-12 格式写成 36 个字符,其整个设计的核心就是即使没有任何机制协调谁获取哪个,也不会有两个相同的 UUID。这个免费生成器在您的浏览器中生成它们:在“版本”中选择一个版本,在“生成数量”中说明您需要多少个,然后按“生成”。页面默认打开的是绝大多数项目所需的单个 v4,因此只需点击一下即可获得第一个标识符。
每个标识符都由您的浏览器通过 Web Crypto API 构建。版本 4 在浏览器支持的情况下使用 crypto.randomUUID(),否则使用 crypto.getRandomValues()——这是一个加密级别强度的随机源,而不是像 Math.random() 这样的 JavaScript 伪随机函数。版本 7 将 48 位的毫秒时间戳放在前面,接着是同一毫秒内递增的 12 位计数器,然后是 62 个随机位,这是 RFC 9562 中描述的排序方法;那个计数器就是为什么一次生成一千个批次会按顺序输出而不是打乱的原因。版本 1 将时间戳与设置了多播位的随机节点标识符结合在一起,因此它永远不会像 1990 年代的原始实现那样携带您的网卡地址。版本 3 和 5 完全不随机:它们使用 MD5 和 SHA-1 分别将一个命名空间与一个名称一起进行散列,并且这种散列处理同样是在您的设备上发生的。没有任何数据被发送到服务器,没有任何记录,而且一旦页面加载完毕,即使断开连接它也会继续工作。
“版本”是决定其他一切的字段。版本 4 是 122 个随机位并且没有结构——这是默认设置,也是当标识符只需要具有唯一性时的正确答案。版本 7 保留了 62 个随机位,但以它的创建时间开头,因此一组 v7 可以按其生成顺序进行排序;正是这一点使得它成为当标识符将用作数据库键时的首选版本。版本 1 是原始的基于时间的方案,保留在这里以供仍要求使用它的系统使用。版本 3 和 5 是特殊的一对:它们是确定性的,这意味着相同的输入总是产生相同的标识符,并且它们需要两个额外的字段——“命名空间”(标准空间 DNS、URL、OID 和 X.500 之一,或您自己的“自定义”空间)和“名称”(被标识的字符串)。为 v5 提供 URL 命名空间和 https://example.com/a,今天、明天以及在别人的机器上您都会得到相同的 UUID。正因为如此,当选择 v3 或 v5 时,“生成数量”会消失:一千份确定性值的副本仍然只是一千份完全相同的副本。对于其他每个版本,“生成数量”可以从 1 到 1000。
四个开关改变了输出的形状,而没有改变底层的 128 位。“大写”将十六进制数字打印为大写字母,“无连字符”提供了某些列和文件名偏好的 32 字符紧凑形式,而“大括号”将值包裹在 { } 中——这是 Windows 和 .NET 世界称为 GUID 的形式。在“高级选项”下,urn:uuid: 添加了使其成为正式 URN 的前缀;当它开启时,会关闭“大括号”和“无连字符”,因为标准只允许一种确切的 URN 形式,那就是带有连字符的规范形式。“复制格式”决定了获取批次时的连接方式:每行一个、以逗号分隔,或者每个值带引号并以逗号结尾,以便直接粘贴到代码的数组中。结果下方有一行报告位数的随机性——v4 为 122 位,v7 为 62 位——并且对于 v7,还会报告批次中第一个标识符内编码的时间戳,因此值内部的时间是可见的,而不是隐含的。“全部复制”和“保存为文件”都遵循“复制格式”的设置。“历史记录”在您的浏览器中保留了您最近的 10 个批次:点击一个即可将其调回,复制或删除单个条目,或清空全部。“清除”会清空结果但保留您的设置,当标识符超过 50 个时,列表会变成一个可滚动的块而不是五十行。
这个页面上最重要的一点就是 v4 和 v7 作为主键的区别,而且这并不是一个品味问题。数据库索引是按排序顺序保持的 B-tree,新键在顺序中落入的位置决定了插入操作的代价。v4 键是随机的,因此连续的插入落在索引中互不相关的角落:已满的页面被拆分,数据库保存在内存中的树的部分很少是下一次插入所需的部分,最终索引变得比其指向的行所对应的规模更大且更碎片化。v7 键以当前毫秒开始,因此连续的插入一起落在树的右边缘,这是自增整数产生的模式,也是索引结构围绕其构建的模式。这就是全部的权衡:v7 为您带来了顺序键的插入行为,同时保留了让您最初选择 UUID 的属性,即任何人都可以在不请求服务器的情况下铸造一个。它的代价是创建时间现在在标识符内部是可读的,而这是否要紧是关于您的数据的问题,而不是关于格式的问题。
没有任何东西会检查 UUID 的唯一性;其尺寸就是保证。这里没有注册表,没有服务器往返,没有查找——生成器产生一个数字并交给您,而其中两个不发生碰撞的原因仅仅是因为有 2^122 种可能的 v4 值,大约为 5.3 × 10^36。生日问题(birthday problem)是衡量它的最诚实方式:您需要大约 2.7 × 10^18 个标识符,才能有 50% 的几率使其中任何两个匹配,这相当于在 86 年里每秒生成十亿个新 UUID。精确了解这承诺了什么和没承诺什么是有价值的。这是一种统计上的保证,而非算术上的,并且它仅在底层的随机性是真实的时候才成立——一个播种不佳的生成器,或一个与其熵池一起被克隆的虚拟机,会以格式无法检测的方式打破这种保证。版本 7 进一步缩小了这个问题:在单个毫秒内,一个生成器根本不会重复自身,因为计数器是递增的而不是掷骰子,而在独立的生成器之间,62 个随机位发挥着作用。
UUID 是标识符,而不是密码。版本 4 是不可预测的,这诱使人们将其视为秘密——密码重置链接、不可猜测的 URL、会话令牌、API 键。不可预测性是真实的,但保密性是值被处理方式的一种属性,而标识符在设计上就是被漫不经心地处理的:它们存在于 URL 中,进入浏览器历史记录、服务器日志、来源标头和分析中,并被粘贴到聊天消息中。其他版本甚至是更糟糕的选择。版本 1 和版本 7 都编码了创建的时间,因此任何持有一个的人都能大致知道它是何时生成的,并能缩小与之一起生成的其他事物的范围。版本 3 和 5 故意是确定性的:一个电子邮件地址的 v5 UUID 不是一个被散列的秘密,它是一个任何拥有相同地址的人都可以在一秒钟内计算出的值。使用 UUID 来命名一个事物。当值必须保持未知时,请使用密码生成器,或为秘密构建的库中的令牌。
为什么要选择这个 UUID 生成器?
- 纯浏览器端生成:标识符在您的设备上产生,绝不会发送到任何地方
- Web Crypto API 随机性:密码学安全,非 Math.random()
- 五种版本集于一处:无需切换工具即可生成 v1、v3、v4、v5 和 v7
- 真正有序的 v7:毫秒内带有计数器,正如 RFC 9562 所描述
- 确定性的 v3 和 v5,配有四个标准命名空间或您自己的自定义空间
- 一次最多 1000 个,超过五十个时提供紧凑的可滚动列表
- 每种常见形状:连字符、纯文本、大写字母、带大括号或 urn:uuid: URN
- 复制格式可选换行、逗号或带引号的值,直接粘贴到代码中
- 无限制且免费:免注册,无使用限制,我们的服务器上不留存任何东西
UUID 出现的场景,通常是在任何东西能够确认名字空闲之前,某物必须被命名的时刻。应用程序代码为即将插入的行铸造它们,以便对象在到达数据库之前就具有标识,并且客户端可以在写入操作仍在进行时引用它。分布式系统更加依赖它们:多个服务同时写入一个表,离线的移动客户端在飞机上创建记录,一个必须识别其已处理消息的队列——所有这些都不能等待共享计数器轮到自己。版本 5 满足了不同的需求,那是一个从您已经拥有的东西衍生出的稳定标识符:同一文档、同一 URL 或同一账户总是映射到相同的 UUID,所以一次导入可以运行两次而不复制任何内容。在其他地方,它们仅仅是系统要求的格式——贯穿日志和追踪的相关性标识符,当一千个用户的上传进入同一个存储桶时不会发生冲突的文件名,一个 Windows 注册表键,一个必须看起来像真实数据的测试夹具。
几个诚实的限制值得了解。一个批次代表一种版本:“版本”中的值适用于批次中的所有内容,因此一次 v4 生成和一次 v7 生成是两个不同的批次。这是一个生成器,而不是检查器——将一个 UUID 粘贴进来以回读它的版本或时间戳并不是本页的功能。“保存为文件”写入纯文本文件,而不是 CSV 或 JSON。版本 7 公开揭示它被创建的毫秒级时间,这是一种真正的权衡而不是缺陷,版本 1 以同样的方式泄漏时间;如果这对您的数据是个问题,版本 4 是那个对谁都不透露任何信息的版本。标准中有两个特殊的值不是版本,所以它们不是“版本”中的选项,但您可以从这里复制它们:nil UUID 即 00000000-0000-0000-0000-000000000000,以及 max UUID 即 ffffffff-ffff-ffff-ffff-ffffffffffff。这个页面上的一切都遵循 RFC 9562,这是 2024 年取代 RFC 4122 的规范,所以输出被任何处理 UUID 的库、数据库或 API 接受。在这些限制内,其保证是强有力的:每个标识符都是按照标准、从密码学安全的随机性中、在您自己的机器上构建的,而且没有其他人看到过它。