UUID、ULID、SnowflakeId、Ksuid、NanoId等编号生成工具功能比较
前言
在设计软件时,数据主键的设计是一个很重要的事情,一条数据的编号关系到数据的唯一性判断与检索定位。
关系数据库理论里面提到了键(key,码)与主键(primary
key,主码),在例子里面一般都使用学号、产品编号等来演示。但并未过多提到这种唯一编号的生成方式。实践中,通常会用随机字符串、自增序列等方式作为唯一编号。
编号不仅会用于软件中的数据存储,也会用在数据交换、人机信息传递等场合。
本文整理几种常见编号生成算法(表格 1),并按照时间顺序介绍这些工具。
汇总表
表格 1 编号生成工具简表
名称(字母顺序) | 长度(bits) | 保留位(bits) | 字符串长度(表示法) | 优化时间排序 | 例子 |
---|---|---|---|---|---|
Ksuid |
160 | 0 | 27(base62) | 是 | 2dJf4bxMcdyAo5UhH3EFJKOt380 |
MongoDB ObjectId |
96 | 0 | 26(hex) | 是 | ObjectId("507f1f77bcf86cd799439011") |
Nano ID |
128 | 2 | 21(base64) | 随机 | ofouG6h5HRadNRgW_atUB |
SnowflakeId |
64 | 1 | 19(10进制) | 是 | 9223372036854775807 |
ULID |
128 | 0 | 26(base32) | 是 | 01HR9YEEJ9JP6TT2SSF72QDTF6 |
UUIDv1 |
128 | 6 | 36(hex) | 随机 | 23ab203a-b193-1016-b803-9600026de545 |
UUIDv4 |
128 | 6 | 36(hex) | 随机 | 07e964b7-3fb5-46fe-b07f-61ec69dbe9ae |
UUIDv6 |
128 | 6 | 36(hex) | 是 | 016b1932-3daa-6b61-b804-45014d54d2ff |
UUIDv7 |
128 | 6 | 36(hex) | 是 | 018e13e7-0e77-7ba6-a0e8-52b6bdb47eb7 |
UUIDv8 |
128 | 6 | 36(hex) | 自定义 | —— |
UUID
Universally Unique Identifier(UUID),全局唯一标识,由RFC4122规范所定义。Windows系统中习惯叫做Globally Unique Identifier (GUID)。该规范在ISO/IEC及ITU-T同样有采用。
UUID可追溯到1980年代,当时Apollo 公司在设计网络计算系统(Network Computing System,NCS)时为了唯一地识别系统中的每个节点,于是提出UUID方案。后来,开放软件基金会(Open Software Foundation,OSF)设计的分布式计算环境(Distributed Computing Environment,DCE)以及微软的Windows平台中均有采用该方案。到1998年时,互联网工作组(Internet Engineering Task Force ,IETF)吸纳其成为标准,编号RFC4122。
UUID实质为一个128bits(16bytes)的二进制数,在其中保留6位用于协议识别。转换成10进制会有39位之长。
为了方便识别和阅读,同时设计一种标准的字符串表示法(16进制):xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
(8-4-4-4-12分隔,每个x实际为0-F的16进制字符)。Windows中还有用大括号括起来的形式{......}
。有人也习惯把分隔符去掉来减少长度,但只有第一种是标准形式。UUID最小是(也叫Nil UUID):00000000-0000-0000-0000-000000000000
,最大是:FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
。
2003年时,注册申请到urn标识,可以写作:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6
。
UUID发展出多个版本来适应不同需求,版本1-5发布于2005年之前。主要差异在于生成思路上,详细说明可以参考规范原文。
UUID v1 与 UUIDv4
UUID第三组前4个位是版本代号,第四组前2位是变体代号。
V1版( Gregorian Time-based UUID)使用机器的公历日期、时钟、随机数与MAC地址组合而成。例如“b375c417-b1ac-1016-b9af-9600026de545
”。
V4版(Random)则全部使用随机数来组合,例如“9960b102-0639-4eef-8c2f-99074a6ccaab
”。“java.util.UUID#randomUUID
”采用的也是这一版设计。
UUIDv3 与 UUIDv5
V3(MD5 Name-based)与V5(SHA-1 Name-based)用于将指定名字空间(如DNS、URL、OID、X500)的名字转换成UUID表示法。不同的是v3采用MD5摘要算法,v5采用SHA-1。
举例:UUIDv3(URL,http://www.baidu.com)=2f67490d-55a4-395e-b540-457195f7aa95
V2(DCE Security)专用于DCE Security ,并未在RFC中讨论。
MongoDB ObjectId
2009年MongoDB数据库发布,里面设计有名为ObjectId的数据类型,也是一个主键生成算法。
ObjectId长度为96bits,由32位的Unix Epoch 时间戳、40位随机数(进程标识),24位顺序递增码(随机开始)组成。
可以表示成24位长的16进制数,例如“ObjectId("507f1f77bcf86cd799439011")
”
SnowflakeId
习惯叫雪花算法。2010 年Twitter 开源了他们团队在用的这种全局唯一ID生成工具。其特点是可用于分布式系统中各节点生成ID,可按照生成时间排序,不连续递增。
SnowflakeId是一个64 bits二进制数。为了避免负数,最高位永远置0,实际使用63 bits。由系统时间戳、机器识别码、顺序号组成。
在SnowflakeId基础上,发展出多种变体,如百度的、美团的、微信的等。
Ksuid
K-Sortable Unique Identifier(Ksuid)。Twilio Inc在比较了UUID、Snowflake ID的特点后,在2017年提出一种新的ID生成算法。特点是可按照生成时间排序,无协调机制,碰撞概率低。
设计为160 bits长,由32位时间戳、128位随机数组合而成,比UUID更长。使用Base62编码方式表示为27位长的字符串。例如“2dtpkbpDpweYidSNJZabF9xo5Cz
”。
NanoId
NanoId ID,2017年,作者是Evil Martians。使用随机数来生成ID,并将数字值编码为字符串(base64),在128bit随机数的情况下,可以编码为21个字符。
字符串长度、字典表均可自行调整,方便在简便情况下生成更短的ID。与其相似的还有squids(曾经叫hashids)
ULID
Universally Unique Lexicographically Sortable Identifier。为了克服UUID(v1-v5)排序不便的问题,民间组织在2017年新提出的一种ID生成算法。
长度128 bits,由前48位时间戳、后80位随机数组成。可按时间排序,利于数据库索引。使用base32编码为长度为26的字符串。例如“01ARZ3NDEKTSV4RRFFQ69G5FAV
”。
UUIDv6-v8
因被吐槽不能按时间排序,不利于数据库索引优化,而新设计的版本。从2021年开始设计,增加3个版本,目前提案已经定稿,等待审定公布。
V6(Reordered Gregorian Time-based) 与 v1生成思路接近,但字节序做了调整以支持时间排序,MAC地址则换成了随机数。
V7(Unix Time-based)进一步采用了Unix Epoch timestamp做时间戳,更接近主流的选择。
V8(Custom)则是一个完全自由的版本。除了固定的6位版本识别外,不定义生成方法,用户可以自己设计决定。这意味着你有122 bits可用,你可以将许多短于这个长度的ID编码成UUID。
未来应该会主推UUIDv7了。
小结
编号本质上是有状态服务,因为其要具备唯一性识别的作用。例如钞票的编号,发票的号码,身份证号码,订单号等。
编号生成一般依赖机器时钟、机器识别码、随机数或者中心化的编号池分配等设计。
在不同需求下编号生成的算法有所不同,自增序列、UUID是常见的方案。
参考资料
[1] https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/14/ —— rfc4122新草案
[2] https://segment.com/blog/a-brief-history-of-the-uuid/ —— UUID历史
[3] https://idtools.co/uuid/v1 —— 在线生成UUID的工具,可用于演示