自学内容网 自学内容网

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
}

原文地址:https://blog.csdn.net/a2025834646/article/details/140233781

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!