冷饭新炒:理解JDK中UUID的底层实现

UUID是Universally Unique IDentifier的缩写,翻译为通用唯一标识符或者全局唯一标识符。对于UUID的描述,下面摘录一下规范文件A Universally Unique IDentifier (UUID) URN Namespace中的一些描述:

UUID(也称为GUID)定义了统一资源名称命名空间。UUID的长度为128比特,可以保证在空间和时间上的唯一性。

动机:

使用UUID的主要原因之一是不需要集中式管理,其中一种格式限定了IEEE 802节点标识符,其他格式无此限制。可以自动化按需生成UUID,应用于多重不同的场景。UUID算法支持极高的分配速率,每台机器每秒钟可以生成超过1000万个UUID,因此它们可以作为事务ID使用。UUID具有固定大小128比特,与其他替代方案相比,它具有体积小的优势,非常适用于各种排序、散列和存储在数据库中,具有编程易用性的特点。

这里只需要记住UUID几个核心特定:

全局时空唯一性

固定长度128比特,也就是16字节(1 byte = 8 bit)

分配速率极高,单机每秒可以生成超过1000万个UUID(实际上更高)

下面就JDK中的UUID实现详细分析一下UUID生成算法。编写本文的时候选用的JDK为JDK11。

再聊UUID

前面为了编写简单的摘要,所以只粗略摘录了规范文件里面的一些章节,这里再详细聊聊UUID的一些定义、碰撞概率等等。

UUID定义

UUID是一种软件构建的标准,也是开放软件基金会组织在分布式计算环境领域的一部分。提出此标准的目的是:让分布式系统中的所有元素或者组件都有唯一的可辨别的信息,因为极低冲突频率和高效算法的基础,它不需要集中式控制和管理唯一可辨别信息的生成,由此,每个使用者都可以自由地创建与其他人不冲突的UUID。

UUID本质是一个128比特的数字,这是一个位长巨大的数值,理论上来说,UUID的总数量为2^128个。这个数字大概可以这样估算:如果每纳秒产生1兆个不相同的UUID,需要花费超过100亿年才会用完所有的UUID。

UUID的变体与版本

UUID标准和算法定义的时候,为了考虑历史兼容性和未来的扩展,提供了多种变体和版本。接下来的变体和版本描述来源于维基百科中的Versions章节和RFC 4122中的Variant章节。

目前已知的变体如下:

变体0xx:Reserved, NCS backward compatibility,为向后兼容做预留的变体

变体10x:The IETF aka Leach-Salz variant (used by this class),称为Leach–Salz UUID或者IETF UUID,JDK中UUID目前正在使用的变体

变体110:Reserved, Microsoft Corporation backward compatibility,微软早期GUID预留变体

变体111:Reserved for future definition,将来扩展预留,目前还没被使用的变体

目前已知的版本如下:

空UUID(特殊版本0),用00000000-0000-0000-0000-000000000000表示,也就是所有的比特都是0

date-time and MAC address(版本1):基于时间和MAC地址的版本,通过计算当前时间戳、随机数和机器MAC地址得到。由于有MAC地址,这个可以保证其在全球的唯一性。但是使用了MAC地址,就会有MAC地址暴露问题。若是局域网,可以用IP地址代替

date-time and MAC address, DCE security version(版本2):分布式计算环境安全的UUID,算法和版本1基本一致,但会把时间戳的前4位置换为POSIX的UID或GID

namespace name-based MD5(版本3):通过计算名字和命名空间的MD5散列值得到。这个版本的UUID保证了:相同命名空间中不同名字生成的UUID的唯一性;不同命名空间中的UUID的唯一性;相同命名空间中相同名字的UUID重复生成是相同的

random(版本4):根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,还有一个特点就是预留了6比特存放变体和版本属性,所以随机生成的位一共有122个,总量为2^122,比其他变体的总量要偏少

namespace name-based SHA-1(版本5):和版本3类似,散列算法换成了SHA-1

其中,JDK中应用的变体是Leach-Salz,提供了namespace name-based MD5(版本3)和random(版本4)两个版本的UUID生成实现

UUID的格式

在规范文件描述中,UUID是由16个8比特数字,或者说32个16进制表示形式下的字符组成,一般表示形式为8-4-4-4-12,加上连接字符-一共有36个字符,例如:

## 例子 123e4567-e89b-12d3-a456-426614174000 ## 通用格式 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

其中4比特长度的M和1到3比特长度的N分别代表版本号和变体标识。UUID的具体布局如下:

属性 属性名 长度(bytes) 长度(16进制字符) 内容
time_low   时间戳低位   4   8   代表时间戳的低32比特的整数表示  
time_mid   时间戳中位   2   4   代表时间戳的中间16比特的整数表示  
time_hi_and_version   时间戳高位和版本号   2   4   高位4比特是版本号表示,剩余是时间戳的高12比特的整数表示  
clock_seq_hi_and_res clock_seq_low   时钟序列与变体编号   2   4   最高位1到3比特表示变体编号,剩下的13到15比特表示时钟序列  
node   节点ID   6   12   48比特表示的节点ID  

基于这个表格画一个图:

冷饭新炒:理解JDK中UUID的底层实现

严重注意,重复三次

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywysp.html