UUID(通用唯一识别码)是由 32 个十六进制数组成的无序字符串,通过一定的算法计算出来。为了保证其唯一性,UUID 规范定义了包括网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成 UUID 的算法。一般来说,算法可以保证任何地方产生的任意一个 UUID 都不会相同,但这个唯一性是有限的,只在特定的范围内才能得到保证。
UUID 的一个非常明显的特点就是本身较长,格式是这样的:
xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx
467e8542-2275-4163-95d6-7adc205580a9
其中 M 位置,代表版本号,由于 UUID 的标准实现有 5 个版本,所以只会是 1、2、3、4、5;
UUID 现有的 5 种版本,是根据不同的使用场景划分的,而不是根据精度,所以 Version5 并不会比 Version1 精度高,在精度上大家都能保证唯一性,重复的概率近乎于 0。
总结:
其中包括了 48 位的 MAC 地址和 60 位的时间戳。且 v1 为了保证唯一性,当时间精度不够时,会使用 13~14 位的 clock sequence 来扩展时间戳,比如:当 UUID 的生产成速率太快,超过了系统时间的精度。时间戳的低位部分会每增加一个 UUID 就 +1 的操作来模拟更高精度的时间戳,换句话说,就是当系统时间精度无会区分 2 个 UUID 的时间先后时,为了保证唯一性,会在其中一个 UUID 上 +1。所以 UUID 重复的概率几乎为 0,时间戳加扩展的 clock sequence 一共有 74bits,(2 的 74 次方,约为 1.8 后面加 22 个零),即在每个节点下,每秒可产生 1630 亿不重复的 UUID。
但由于 v1 中最后的 12 位是网卡的 MAC 地址,会导致隐私问题以及安全问题,这是这个版本 UUID 受到批评的地方。
DCE(Distributed Computing Environment)安全的 UUID 和基于时间的 UUID 算法相同,但会把时间戳的前 4 位置换为 POSIX 的 UID 或 GID。这个版本的 UUID 在实际中较少用到。
v3 和 v5 都是通过计算 namespace 和名称的哈希值生成的。不同的点在于 v3 使用的 hash 算法为 MD5,v5 使用 SHA-1。因为算法中没有不确定的部分,所以当 namespace 与名称确定时,得到的 UUID 都是确定唯一的。比如:
$ uuid -n 3 -v3 ns:URL www.jd.com
7e963853-8fce-3085-bb2c-8424745d73a2
7e963853-8fce-3085-bb2c-8424745d73a2
7e963853-8fce-3085-bb2c-8424745d73a2
算法实现中会将 namespace 和输入参数拼接在一起,计算 hash 结果,再进行截断格式化等操作来保证唯一性。
v4 的 UUID 中 4 位代表版本,2-3 位代表 variant。余下的 122-121 位都是全部随机的。即有 2 的 122 次方 (5.3 后面 36 个 0) 个 UUID。一个标准实现的 UUID 库在生成了 2.71 万亿个 UUID 会产生重复 UUID 的可能性也只有 50% 的概率。这相当于每秒产生 10 亿的 UUID,持续 85 年,而把这些 UUID 都存入文件,每个 UUID 占 16bytes,总需要 45EB(exabytes),比目前最大的数据库 (PB) 还要大很多倍。
在 java 中使用 v4:
# java 1.5+
# java.util.UUID
for (int i = 0; i < 3; i++) {
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
}
生成的 UUID 如下:
8bca474b-214d-4ce8-8446-b99f30147f94
c38588cf-a1c4-4758-9d86-b2ee5552ae59
febf5a46-bd1b-43f8-89a8-d5606e5d1ce0
由于这个版本使用非常简单,因此使用最为广泛。
雪花算法,是 Twitter 开源的分布式 ID 生成算法。雪花算法中利用了时间戳,机器 ID,以及同毫秒内的不同序列号来保证分布式生成 ID 的唯一性。
雪花算法总结
1.时间戳在高位,自增序列在低位的特征可以保证整个 ID 的趋势是递增有序的。
2.但由于其依赖机器时钟,如果机器时钟回拨,可能会导致重复 ID 生成。其在分布式环境下,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况。
SnowFlake 算法生成 id 的结果是一个 64bit 大小的整数,它的结构如下图:
使用 UUID 这些随机 ID 生成算法作为 MySQL 主键的生成方案呢?答案是:不可以!
众所周知,当 MySQL 数据表使用 InnoDB 作为存储引擎时,每一个索引都对应一个 B+ 树,若表定义了主键(没有时,MySQL 则会自动生成不可见的自增主键),主键对应的索引就是聚簇索引,表的所有数据都存储在聚簇索引上。索引中键值的逻辑顺序决定了表中相应行的物理顺序(索引中的数据物理存放地址和索引的顺序是一致的)。可以这么理解:只要是索引是连续的,那么数据在存储介质上的存储位置也是连续的。
基于以上特性,由于自增键的值是有序的,插入数据时,Innodb 会把每一条记录都存储在上一条记录的后面。当达到页面的最大填充因子时候 (innodb 默认的最大填充因子是页大小的 15/16,会留出 1/16 的空间留作以后的修改),会进行如下操作:下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费;且由于新插入的行一定会在原有的最大数据行下一行,mysql 定位和寻址很快,不会为计算新行的位置而做出额外的消耗。
而 UUID 相对于有序的自增 ID,它的值是毫无规律可言的,新行的主键不一定要比之前数据主键的值大,所以 innodb 无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。这个过程需要做很多额外的操作,而且最终分布散乱的数据会导致以下问题:
作者:京东零售 金越
来源:京东云开发者社区 转载请注明来源