分布式的Unique ID,基本要求:
- 唯一性
- 时间相关
- 粗略有序
- 可反解
Universally Unique IDentifier(UUID),有着正儿八经的RFC规范,是一个128bit的数字,也可以表现为32个16进制的字符(每个字符0-F的字符代表4bit),中间用"-"分割。
- 时间戳+UUID版本号: 分三段占16个字符(60bit+4bit),
- Clock Sequence号与保留字段:占4个字符(13bit+3bit),
- 节点标识:占12个字符(48bit),
比如:
f81d4fae-7dec-11d0-a765-00a0c91e6bf6
实际上,UUID一共有多种算法,能用于TraceId的是:
- version1: 基于时间的算法
- version4: 基于随机数的算法
version 4 基于随机数的算法
这是最暴力的做法,也是JDK里的算法,不管原来各个位的含义了,除了少数几个位必须按规范填,其余全部用随机数表达。
JDK里的实现,用 SecureRandom生成了16个随机的Byte,用2个long来存储。
public static UUID randomUUID() {
SecureRandom ng = Holder.numberGenerator;
byte[] randomBytes = new byte[16];
ng.nextBytes(randomBytes);
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |= 0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |= 0x80; /* set to IETF variant */
return new UUID(randomBytes);
}
使用示例:
public static void main(String[] args) throws Exception {
UUID idOne = UUID.randomUUID();
UUID idTwo = UUID.randomUUID();
// 352e6f6c-5b2c-4fc8-a084-cc1ff3170877
System.out.println(idOne.toString());
// a3c895f7-298c-403b-9114-b86197ec875e
System.out.println(idTwo.toString());
}
version 1 基于时间的算法
严格守着原来各个位的规矩:
- 时间戳:有满满的60bit,所以可以尽情花,以100纳秒为1
- 顺序号: 这16bit则仅用于避免前面的节点标示改变(如网卡改了),时钟系统出问题(如重启后时钟快了慢了),让它随机一下避免重复。
- 节点标识:48bit,一般用MAC地址表达,如果有多块网卡就随便用一块。如果没网卡,就用随机数凑数,或者拿一堆尽量多的其他的信息,比如主机名什么的,拼在一起再hash一把。
但这里的顺序号并不是我们想象的自增顺序号,而是一个一次性的随机数,所以如果两个请求在同100个纳秒发生时。。。。。
还有这里的节点标识只在机器级别,没考虑过一台机器上起了两个进程。
所以严格的Version1没人实现,接着往下看各个变种吧。