Go 语言 UUID 库 google/uuid 源码解析:UUID version1 的实现

google/uuid 库地址

关于 UUID 的总体介绍可以查看这篇文章,其包含阅读此篇文章的前置内容。

UUID version 1 在 RFC 4122 文件中定义,其实现基于节点 ID、时钟序列以及当前时间(距离格里历改日【1582年10月15日】 的100纳秒数,具体介绍可以看Go 语言 UUID 库 google/uuid 源码解析:时钟信息)。

目前还没有详细的文章介绍节点 ID 的实现,但是可以知道的是,节点 ID 是利用网络接口硬件地址生成的,定义在 node.go 文件的 setNodeInterface 函数中。其逻辑大致如下:

  1. 如果你指定了网络接口的名称,则它回尝试获取该接口的硬件地址(即 MAC 地址)作为节点 ID。
  2. 如果没有指定,则选择第一个可用接口的硬件地址。
  3. 如果没有可用的硬件地址则会随机生成一个节点 ID。

UUID version 1 在 google/uuid 中的实现则定义在 version1.go 文件中。

函数接口

UUID version 1 定义的接口为 NewUUID(),其返回值为 (UUID, error) 即返回 UUID 序列以及错误信息。其具体代码放在文章末尾,存在困惑的地方,可以看看源码。

具体实现

UUID 的存储结构

首先我们知道 UUID 实际是长 16 字节的序列,其表现是 32 个十六进制数。google 则是将 UUID 序列使用长 16 的字节切片进行存储。其实现如下:

  1. 首先在 uuid.go 文件中声明 type UUID [16]byte 将长 16 的字节切片起别名为 UUID,使其含义更加清晰。
  2. 然后在 version1.go 文件 NewUUID 函数中定义 uuid 变量供后续使用 var uuid UUID

获取时间与时钟序列

时间戳与时钟序列通过 GetTime() 函数直接获取。(GetTime() 的详细介绍可以看 Go 语言 UUID 库 google/uuid 源码解析:时钟信息)。得到两个变量 nowseqnow, seq, err := GetTime()

分割时间信息

首先我们需要知道获取到的 now 类型为 int64 ,即其二进制有 64 位,uuid 中的时间信息会被“切割”为三段:timeHi(16)、timeMid(16)、timeLow(32),具体“切割”如下:

xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
timeHi		    /timeMid		 /timeLow

需要提起知道的是类似 x & 0xff 的代码用于保留低位比特值,抹除高位比特值。示例如下:

x:      10101010 11011011
0xff:   00000000 11111111
-------------------------
&:      00000000 11011011

“切割”实现代码如下:

// 低32位
timeLow := uint32(now & 0xffffffff)
// 高32位中的低16位
timeMid := uint16((now >> 32) & 0xffff)
// 高16位
timeHi := uint16((now >> 48) & 0x0fff)

上述代码详解如下:

  • timeLow
    1. now & 0xffffffff:取 now(int64) 的低 32 位。
    2. uint32(x):将结果转为 uint32。
  • timeMid
    1. now >> 32 将 now(int64) 的高 32 位挪到低 32 位,高 32 位置 0。
    2. (x) & 0xffff 取当前(新)低 32 位中的低 16 位。
    3. uint16(x) 将结果转为 uint 16。
  • timeHi
    1. now >> 48 将 now(int64) 的高 16 位挪到低 16 位,高 48 位置 0。
    2. (x) & 0xfff 取当前(新)低 16 位中的低 12 位。
    3. uint16(x) 将结果转位 uint 16。

之所以 timeHi 只取到低 12 位,是因为需要保留 4 位作为标志位,此次是用于标识 UUID 版本。

我们需要提前知道的是:类似于 x |= 0x1000 的代码,使用于将某个特殊位置为 1 的,此次是将第 13 位(从右往左)置为1:

x:       00000011 00110011
0x1000:  00010000 00000000
--------------------------
|:       00010000 00110011

标识版本代码如下:

timeHi |= 0x1000 // 版本 1

将时间信息和时钟序列放置到 uuid 的正确位置

首先我们需要知道最终的 uuid 结构组成如何:

(🂓代表标志位)

十六进制字符数|8							     |4				  |4  🂓  		   |4			    |12
二进制数     |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
具体含义     |timeLow						 |timeMid		  |timeHi		   |seq			    |clockSeq								       

我们只需要按照这个结构以大端序的方式(RFC 变体使用大端序)依此填入即可,实现代码如下:

// 将 timeLow 填充到前 4 个字节
binary.BigEndian.PutUint32(uuid[0:], timeLow)
// 将 timeMid 填充到第4和第5个字节
binary.BigEndian.PutUint16(uuid[4:], timeMid)
// 将 timeHi 填充到第6和第7个字节
binary.BigEndian.PutUint16(uuid[6:], timeHi)
// 将时钟序列填充到第8和第9个字节
binary.BigEndian.PutUint16(uuid[8:], seq)

填充 NodeID

时间和时钟序列填充完毕之后,最后只需填充 NodeID 即可。其基本逻辑为:

  1. 加锁
  2. 如果当前 nodeID 未设置,则通过 setNodeInterface 生成。
  3. 将 nodeID 拷贝至 uuid 的第10到最后一个节点。
  4. 解锁

实现代码如下:

	nodeMu.Lock()
	if nodeID == zeroID {
		setNodeInterface("")
	}
	copy(uuid[10:], nodeID[:])
	nodeMu.Unlock()

返回 uuid

最后返回填充好的 uuid 和 nil(error) 即可。return uuid, nil

到这里,完整的 UUID version1 源码解析便完成了,希望你能有所收获。

NewUUID 源码

func NewUUID() (UUID, error) {
	var uuid UUID
	now, seq, err := GetTime()
	if err != nil {
		return uuid, err
	}
	
	// 标志位
	//   🂓
	// xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
	// timeHi		   /timeMid			/timeLow

	// 低32位
	timeLow := uint32(now & 0xffffffff)
	// 高32位中的低16位
	timeMid := uint16((now >> 32) & 0xffff)
	// 高16位
	timeHi := uint16((now >> 48) & 0x0fff)
	// 将第4位置为1,作为标志位,标志为版本号1
	timeHi |= 0x1000 // 版本 1

	// 8							   /4				/4  🂓  		  /4			   /12 											    /16进制字符数
	// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/二进制数
	// timeLow						   /timeMid			/timeHi			 /seq			  /clockSeq									       /具体含义
	binary.BigEndian.PutUint32(uuid[0:], timeLow)
	binary.BigEndian.PutUint16(uuid[4:], timeMid)
	binary.BigEndian.PutUint16(uuid[6:], timeHi)
	binary.BigEndian.PutUint16(uuid[8:], seq)

	nodeMu.Lock()
	if nodeID == zeroID {
		setNodeInterface("")
	}
	copy(uuid[10:], nodeID[:])
	nodeMu.Unlock()

	return uuid, nil
}
  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值