google/uuid 库地址
本文将解析 googl/uuid 库中 UUID 变体10版本4的实现。
版本 4 的 UUID 采取完全随机的方式实现,简单来说就是将 UUID 中的 122 位全部随机填充(剩余的 6 位作标记位)。版本 4 的 UUID 存在一定的重复风险,但就如源码注释中所说:“一年内创建几十万亿个 UUID 并出现一个重复的概率,就如同一个人一年被流星击中的风险一样(估计为 170 亿分之一)”
UUID 版本4 的实现代码写在 version4 中,提供了两种不同的实现方式,一种是直接通过随机生成器 io.Reader 生成,另一种则是通过随机池生成。
无随机池生成(随机生成器直接生成)
这种生成方法定义在 NewRandomFromReader 中,NewRandomFromReader 接收 io.Reader 作为参数,并返回 UUID 和 error。其代码很简单:
// NewRandomFromReader 根据从给定 io.Reader 读取的字节返回一个 UUID。
func NewRandomFromReader(r io.Reader) (UUID, error) {
var uuid UUID
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // 版本 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // 变体为 10
return uuid, nil
}
可以拆解为两部分,一部分随机填充 UUID,另一部分标志版本和变体。
随机填充最关键的部分便是 io.Reader 和 io.ReadFull。
Reader 是定义在 io 包中的接口,是所有输入操作的基础,它是读取数据行为的抽象。在 Reader 接口中存在一个 Read 方法,会将读取到的是数据输出到字节切片 p 中:
type Reader interface {
Read(p []byte) (n int, err error)
}
任何实现了 Read 方法的对象都可以视为一个 Reader。
而 NewRandomFromReader 函数由另一个内部函数调用,使用的是 rand.Reader 即定义在 rand.go 中的 Reader,其 “Reader 是一个全局的,共享的密码学安全随机数生成器实例。”(go 源码注释)
而 io.ReadFull 是一个辅助函数,用于辅助 io.Read 将切片填满。
所以 _, err := io.ReadFull(r, uuid[:])
的含义便是使用密码学安全的随机数填满切片 uuid(16 字节的切片)。
而标识部分可以查看此篇文章Go 语言 UUID 库 google/uuid 源码解析:UUID version1 的实现的 分割时间信息
的末尾。
随机池生成
这种生成方法定义在 newRandomFromPool 中 代码如下:
func newRandomFromPool() (UUID, error) {
var uuid UUID
poolMu.Lock()
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
}
copy(uuid[:], pool[poolPos:(poolPos+16)])
poolPos += 16
poolMu.Unlock()
uuid[6] = (uuid[6] & 0x0f) | 0x40 // 版本 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // 变体为 10
return uuid, nil
}
可以也拆解为两部分,一部分从随机池读取 UUID,另一部分标志版本和变体。
从随机池读取 UUID 的原理就是预设一个随机池并预填充数据,当需要数据时,便从随机池中截取数据。而随机池也是一个切片 pool = [randPoolSize]byte
,而 randPoolSize 则硬编码为 16 * 16,所以随机池其实是一个 16 * 16 字节的切片。与 pool
配合使用的还有 poolMu
与 poolPos
,poolMu 用于避免并访问随机池切片,而 poolPos 则是用于指定当前 UUID 的切片范围,每次生成 UUID 时即从随机池中拷贝 poolPos
到 poolPos + 16
的数据(copy(uuid[:], pool[poolPos:(poolPos+16)])
),然后 poolPos += 16
更改下次 UUID 的范围来避免获取到相同的数据。如果判断到已经到达随机池的上限了,则使用 io.ReadFull 重新填充随机池。
if poolPos == randPoolSize {
_, err := io.ReadFull(rander, pool[:])
if err != nil {
poolMu.Unlock()
return Nil, err
}
poolPos = 0
}
值得一提的是 poolPos
在定义的时候便是 poolPos = randPoolSize
,所以在第一次生成 UUID 的时候便是随机池第一次被填充的时候。
如何决定什么时候使用随机池
对于 version4 还有个重要的参数就是布尔类型的 poolEnabled
,poolEnabled
用于指定是否使用随机池,其默认值为 false
,其搭配使用的是 EnableRandPool
和 DisableRandPool
,分别用于启动缓冲池和关闭缓冲池,其实现逻辑简单说就是将 poolEnabled
设置为 true
或 false
。
而利用到 poolEnabled
的是函数 NewRandom
:
func NewRandom() (UUID, error) {
if !poolEnabled {
return NewRandomFromReader(rander)
}
return newRandomFromPool()
}
poolEnabled
为 true
则调用 newRandomFromPool
,否则调用NewRandomFromReader
。
而 NewRandom
再往上层封装便是用户接口 New
了:
func New() UUID {
return Must(NewRandom())
}
此处的 Must 用处便是检查 NewRandow 是否返回了 error。
到这里一个完整的 UUID 版本4便完成了。
以上就是 UUID 版本4实现的所有内容,希望你能有所收获。