UUID介绍与生成的方法
什么是UUID?
UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符。UUID具有以下涵义:
- 经由一定的算法机器生成
为了保证UUID的唯一性,规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成UUID的算法。UUID的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。
- 非人工指定,非人工识别
UUID是不能人工指定的,除非你冒着UUID重复的风险。UUID的复杂性决定了“一般人“不能直接从一个UUID知道哪个对象和它关联。
- 在特定的范围内重复的可能性极小
UUID的生成规范定义的算法主要目的就是要保证其唯一性。但这个唯一性是有限的,只在特定的范围内才能得到保证,这和UUID的类型有关(参见UUID的版本)。
UUID是16字节128位长的数字,通常以36字节的字符串表示,示例如下:
3F2504E0-4F89-11D3-9A0C-0305E82C3301
其中的字母是16进制表示,大小写无关。
Universally Unique IDentifier(UUID),有着正儿八经的RFC规范,是一个128bit的数字,也可以表现为32个16进制的字符,中间用”-”分割。
- 时间戳+UUID版本号,分三段占16个字符(60bit+4bit),
- Clock Sequence号与保留字段,占4个字符(13bit+3bit),
- 节点标识占12个字符(48bit),
GUID(Globally Unique Identifier)是UUID的别名;但在实际应用中,GUID通常是指微软实现的UUID。
UUID的版本
UUID具有多个版本,每个版本的算法不同,应用范围也不同。
首先是一个特例--Nil UUID--通常我们不会用到它,它是由全为0的数字组成,如下:
00000000-0000-0000-0000-000000000000
UUID Version 1:基于时间的UUID
因为时间戳有满满的60bit,所以可以尽情花,以100纳秒为1,从1582年10月15日算起(能撑3655年,真是位数多给烧的,1582年有意思么)
节点标识也有48bit,一般用MAC地址表达,如果有多块网卡就随便用一块。如果没网卡,就用随机数凑数,或者拿一堆尽量多的其他的信息,比如主机名什么的,拼在一起再hash一把。
顺序号这16bit则仅用于避免前面的节点标示改变(如网卡改了),时钟系统出问题(如重启后时钟快了慢了),让它随机一下避免重复。
但好像Version 1就没考虑过一台机器上起了两个进程这类的问题,也没考虑相同时间戳的并发问题,所以严格的Version1没人实现,接着往下看各个变种吧。
Version1变种 – Hibernate
Hibernate的CustomVersionOneStrategy.java,解决了之前version 1的两个问题
- 时间戳(6bytes, 48bit):毫秒级别的,从1970年算起,能撑8925年….
- 顺序号(2bytes, 16bit, 最大值65535): 没有时间戳过了一秒要归零的事,各搞各的,short溢出到了负数就归0。
- 机器标识(4bytes 32bit): 拿localHost的IP地址,IPV4呢正好4个byte,但如果是IPV6要16个bytes,就只拿前4个byte。
- 进程标识(4bytes 32bit): 用当前时间戳右移8位再取整数应付,不信两条线程会同时启动。
值得留意就是,机器进程和进程标识组成的64bit Long几乎不变,只变动另一个Long就够了。
Version1变种 – MongoDB
MongoDB的ObjectId.java
- 时间戳(4 bytes 32bit): 是秒级别的,从1970年算起,能撑136年。
- 自增序列(3bytes 24bit, 最大值一千六百万): 是一个从随机数开始(机智)的Int不断加一,也没有时间戳过了一秒要归零的事,各搞各的。因为只有3bytes,所以一个4bytes的Int还要截一下后3bytes。
- 机器标识(3bytes 24bit): 将所有网卡的Mac地址拼在一起做个HashCode,同样一个int还要截一下后3bytes。搞不到网卡就用随机数混过去。
- 进程标识(2bytes 16bits):从JMX里搞回来到进程号,搞不到就用进程名的hash或者随机数混过去。
可见,MongoDB的每一个字段设计都比Hibernate的更合理一点,比如时间戳是秒级别的。总长度也降到了12 bytes 96bit,但如果果用64bit长的Long来保存有点不上不下的,只能表达成byte数组或16进制字符串。
另外对Java版的driver在自增序列那里好像有bug。
Twitter的snowflake派号器
snowflake也是一个派号器,基于Thrift的服务,不过不是用redis简单自增,而是类似UUID version1,
只有一个Long 64bit的长度,所以IdWorker紧巴巴的分配成:
- 时间戳(42bit) 自从2012年以来(比那些从1970年算起的会过日子)的毫秒数,能撑139年。
- 自增序列(12bit,最大值4096), 毫秒之内的自增,过了一毫秒会重新置0。
- DataCenter ID (5 bit, 最大值32),配置值。
- Worker ID ( 5 bit, 最大值32),配置值,因为是派号器的id,所以一个数据中心里最多32个派号器就够了,还会在ZK里做下注册。
可见,因为是派号器,把机器标识和进程标识都省出来了,所以能够只用一个Long表达。
另外,这种派号器,client每次只能一个ID,不能批量取,所以额外增加的延时是问题。
UUID Version 2:DCE安全的UUID
DCE(Distributed Computing Environment)安全的UUID和基于时间的UUID算法相同,但会把时间戳的前4位置换为POSIX的UID或GID。这个版本的UUID在实际中较少用到。
UUID Version 3:基于名字的UUID(MD5)
基于名字的UUID通过计算名字和名字空间的MD5散列值得到。这个版本的UUID保证了:相同名字空间中不同名字生成的UUID的唯一性;不同名字空间中的UUID的唯一性;相同名字空间中相同名字的UUID重复生成是相同的。
UUID Version 4:随机UUID
根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,但随机的东西就像是买彩票:你指望它发财是不可能的,但狗屎运通常会在不经意中到来。
UUID Version 5:基于名字的UUID(SHA1)
和版本3的UUID算法类似,只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。
UUID的应用
从UUID的不同版本可以看出,Version 1/2适合应用于分布式计算环境下,具有高度的唯一性;Version 3/5适合于一定范围内名字唯一,且需要或可能会重复生成UUID的环境下;至于Version 4,我个人的建议是最好不用(虽然它是最简单最方便的)。
通常我们建议使用UUID来标识对象或持久化数据,但以下情况最好不使用UUID:
- 映射类型的对象。比如只有代码及名称的代码表。
- 人工维护的非系统生成对象。比如系统中的部分基础数据。
对于具有名称不可重复的自然特性的对象,最好使用Version 3/5的UUID。比如系统中的用户。如果用户的UUID是Version 1的,如果你不小心删除了再重建用户,你会发现人还是那个人,用户已经不是那个用户了。(虽然标记为删除状态也是一种解决方案,但会带来实现上的复杂性。)
UUID使用方法
#生成基于计算机主机ID和当前时间的UUID print(uuid.uuid1()) #基于命名空间和一个字符的MD5加密的UUID print(uuid.uuid3(uuid.NAMESPACE_DNS,'alaji')) #随机生成一个UUID print(uuid.uuid4()) #基于命名空间和一个字符的SHA-1加密的UUID print(uuid.uuid5(uuid.NAMESPACE_DNS,'alaji')) #需要转换成str类型,下面说明
结果
98345eb4-da04-11e8-9133-54ab3a0caf8d 171898c7-77dd-3258-99f8-41664402c08a 31d7df8f-cb78-41aa-bd4b-b73db34f9358 a08d35bf-65b8-558f-beea-369294bebbd8
UUID是128位的全局唯一标识符,通常由32字节的字符串表示。它可以保证时间和空间的唯一性,也称为GUID,全称为:UUID —— Universally Unique IDentifier,Python 中叫 UUID。
它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。
UUID主要有五个算法,也就是五种方法来实现。
- uuid1()——基于时间戳。由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性,但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。
- uuid2()——基于分布式计算环境DCE(Python中没有这个函数)。算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。实际中很少用到该方法。
- uuid3()——基于名字的MD5散列值。通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。
- uuid4()——基于随机数。由伪随机数得到,有一定的重复概率,该概率可以计算出来。
- uuid5()——基于名字的SHA-1散列值。算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法。