go语言UUID包 // UUID package for Go

名称 go.uuid
地址 Github
作者 satori等
brief intro UUID package for Go
简要介绍 go语言UUID包
LICENSE MIT
Stars 1304

什么是UUID

UUID的全称是universally unique identifier,全局唯一认证,它是一个128比特的数字,用来辨认计算机系统中的信息。术语GUID也是这个意思。

UUID的终极目的是“独特性”,它不希望依赖中央注册机构或协调双方之间产生这种独特性,与大多数其他编号计划。虽然UUID将重复的概率不为零,但是它接近于零,因此可以忽略不计。

在命令行,有简单的产生UUID的方法,在Unix/Linux系统中,只要简单的执行uuidgen就可以获得一个新的UUID字符串。

1
2
$ uuidgen
7d976a86-8414-11e7-8ac3-6c92bf136d47

UUID是128比特的数字,如果用16进制表示的话,可以表示为一个32位的十六进制数。一般在格式化的时候,格式化为8-4-4-4-12的形式,如

123e4567-e89b-12d3-a456-426655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

为什么要有UUID

在分布式系统中,因为通信、同步、保持一致性具有很高的成本,所以一些场景下希望不依靠通信就可以生成唯一的身份验证符号。

如何产生UUID

那么这样一个UUID是如何产生的呢?

中国是一个超大规模的国家,如果每个人生成自己的身份证,都需要到相互协调,那么全国肯定一团糟,如果每个人都到中央注册机构来确定独特性的话,那么中央注册机构肯定被挤爆。那么身份证的生成办法是分配前几位数为地区,分配中间几位为时间,后面三位用于一个区域内确定唯一性,最后一位校验即可。这样就可以相对方便地产生唯一的身份号码了。

同样地,MAC地址和蓝牙地址的确定也是类似的策略,前面的位数是厂商号,后面的位数由厂商内部决定,这样就避免了冲突。

实际上,UUID的国际标准RFC 4122定义了5个版本的生成方式。

版本1

根据时间和MAC地址生成。版本1会把48比特的MAC地址和60比特的时间戳串联起来。
这个时间戳是从1582年10月15日午夜UTC时间起算起的纳秒数目。
按照RFC4122的规定,这个时间可以延续到大概公元3400年。
然而,一些软件,譬如libuuid库,把时间戳作为非负数类型使用,在这种方法下可以使用到公元5236年。

版本2

根据时间和MAC地址生成,并使用POSIX UID/GID,DCE安全版本。
版本2和版本1很类似,除了最重要的八个时钟序列字节被替换为了本地域的数字,最护重要的32个时间戳被替换为与本地域对应的整形数字。在POSIX系统上,它与UID和GID有关。在非POSIX系统上,就靠系统自己定义了。

版本3

哈希命名空间和名字,使用MD5作为哈希算法。
版本3的生成方法是哈希命名空间的标识符和名字。命名空间标识符本身就是一个UUID,该规格提供UUID来标识URL的命名空间等作用。

版本4

随机生成。
忘了说,上述所有版本生成的UUID都有字段来规定自己的版本。所以并非128比特都是可以任意使用的。
对于随机生成的版本4UUID,理论上有2122, 或者说5.3x1036种可能性。

版本5

哈希命名空间和名字,使用SHA1作为哈希算法。和版本3特别类似。因为SHA1算法产生的字串是160bit,比MD5长,所以强行缩短为128位再插入。

冲突

在版本1或者2中,因为是和独一无二的MAC地址相关的,所以只要MAC地址不冲突,则UUID不可能冲突。当然MAC地址是可以伪造的,那个时候冲突就不在我们的讨论范围了。

但是其他版本的是有可能产生冲突的。譬如版本4,完全依靠随机性来产生,两个UUID一模一样是存在理论可能的。
然而,因为位数较多,所以这个可能性极低。
经过计算,产生的2.71x1018个UUID,有50%的概率会拥有至少一个冲突。

UUID冲突公式

这概率可以说实在是太低了,一般应用场景不会有这种问题。

go.uuid

最后来讲解一下这个库的用法。这个库相对比较优势的地方就是它实现了RFC4122规定的所有版本,虽然并没有很难哈哈。
使用样例很简单,这里是版本4,也是最简单的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
"github.com/satori/go.uuid"
)
func main() {
// Creating UUID Version 4
u1 := uuid.NewV4()
fmt.Printf("UUIDv4: %s\n", u1)
// Parsing UUID from string input
u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
if err != nil {
fmt.Printf("Something gone wrong: %s", err)
}
fmt.Printf("Successfully parsed: %s", u2)
}

参考文献: