唯一ID生成算法剖析,看看这篇就够了( 二 )


版本号:版本号即上文所说的五个版本,在五个版本的UUID中,都总是在该位置标识版本,占据 4-bit,分别以下列数字表示:

唯一ID生成算法剖析,看看这篇就够了

文章插图
因此版本号这一位的取值只会是1,2,3,4,5
变体值:表明所依赖的标准(X表示可以是任意值):
唯一ID生成算法剖析,看看这篇就够了

文章插图
时钟序列:在基于时间的UUID中,时钟序列占据了07~06位的14-bit 。不同于时间值,时钟序列实际上是表示一种逻辑序列,用于标识事件发生的顺序 。在此,如果前一时钟序列已知,则可以通过自增来实现时钟序列值的改变;否则,通过(伪)随机数来设置 。主要用于避免因时间值向未来设置或节点值改变可能导致的UUID重复问题 。
节点值:在基于时间的UUID中,节点值占据了05~00的48-bit,由机器的MAC地址构成 。如果机器有多个MAC地址,则随机选其中一个;如果机器没有MAC地址,则采用(伪)随机数 。
了解了基于时间的UUID结构及生成规则后,再看看其他版本的UUID生成规则:
  • 版本2 - 分布式安全的UUID:
将基于时间的UUID中时间戳前四位换为POSIX的UID或GID,其余保持一致 。
  • 版本3/5 - 基于名字空间的UUID (MD5/SHA1):
  1. 将命名空间 (如DNS、URL、OID等) 及名字转换为字节序列;
  2. 通过MD5/SHA1散列算法将上述字节序列转换为16字节哈希值 (MD5散列不再推荐,SHA1散列的20位只使用其15~00位);
  3. 将哈希值的 3~0 字节置于UUID的15~12位;
  4. 将哈希值的 5~4 字节置于UUID的11~10位;
  5. 将哈希值的 7~6 字节置于UUID的09~08位,并用相应版本号覆盖第9位的高4位 (同版本1位置);
  6. 将哈希值的 8 字节置于UUID的07位,并用相应变体值覆盖其高2位 (同版本1位置);
  7. 将哈希值的 9 字节置于UUID的06位 (原时钟序列位置);
  8. 将哈希值的 15~10 字节置于UUID的05~00位 (原节点值位置) 。
  • 版本4 - 基于随机数的UUID:
  1. 生成16byte随机值填充UUID 。重复机率与随机数产生器的质量有关 。若要避免重复率提高,必须要使用基于密码学上的假随机数产生器来生成值才行;
  2. 将变体值及版本号填到相应位置 。
 
5.多版本伪码// 版本 1 - 基于时间的UUID:gen_uuid { struct uuid uu; // 获取时间戳 get_time(&clock_mid, &uu.time_low); uu.time_mid = (uint16_t) clock_mid; // 时间中间位 uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000; // 时间高位 & 版本号 // 获取时钟序列 。在libuuid中,尝试取时钟序列+1,取不到则随机;在Python中直接使用随机 get_clock(&uu.clock_seq);// 时钟序列+1 或 随机数 uu.clock_seq |= 0x8000;// 时钟序列位 & 变体值 // 节点值 char node_id[6]; get_node_id(node_id);// 根据mac地址等获取节点id uu.node = node_id; return uu;}// 版本4 - 基于随机数的UUID:gen_uuid { struct uuid uu; uuid_t buf; random_get_bytes(buf, sizeof(buf));// 获取随机出来的uuid,如libuuid根据进程id、当日时间戳等进行srand随机 uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;// 变体值覆盖 uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000;// 版本号覆盖 return uu;}// 版本5 - 基于名字空间的UUID(SHA1版):gen_uuid(name) { struct uuid uu; uuid_t buf; sha_get_bytes(name, buf, sizeof(buf));// 获取name的sha1散列出来的uuid uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;// 变体值覆盖 uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x5000;// 版本号覆盖 return uu;}(左滑查看完整代码)
数据库自增ID
数据库自增ID可能是大家最熟悉的一种唯一ID生成方式,其具有使用简单,满足基本需求,天然有序的优点,但也有缺陷:
  • 并发性不好
  • 数据库写压力大
  • 数据库故障后不可使用
  • 存在数量泄露风险
因此这里给出两种优化方案 。
 
1. 数据库水平拆分,设置不同的初始值和相同的步长


推荐阅读