比UUID更快:如何生成分布式唯一时间戳标识符 - vanillajava

22-01-15 banq

本文介绍了一个直接支持分布式标识符生成的实现。

1. 分布式系统中的并发标识符生成
每个主机都有一个预定义的唯一主机标识符或hostId 。

TimeProvider[url=https://github.com/OpenHFT/Chronicle-Core/blob/ea/src/main/java/net/openhft/chronicle/core/time/TimeProvider.java] [/url]提供了同时最多100 个主机生成不同的主机标识符。 
 
2. 带有主机标识符的纳秒时间戳

DistributedUniqueTimeProvider将主机标识符存储在时间戳的低两位数字中,使其更易于阅读。
这使您可以在多达 100 台机器上生成有保证的唯一标识符。
同一台机器上的多个 JVM 共享一个 hostId。
时间戳在 hostId 为 28 的机器上看起来像这样:

2021-12-28T14:07:02.9541001 28

前面是时间:日期/时间/微秒
最后两位数字是主机标识符
这样更容易在时间戳中查看来源。
这提供了十分之一微秒(数百纳秒)的分辨率。 无论如何,这通常是许多系统中可用挂钟的限制。
默认情况下,可以在命令行上使用-DhostId= xx将hostId设置为系统属性,或者通过调用 

DistributedUniqueTimeProvider.INSTANCE.hostId(hostId) ;

 

使用主机标识符加速分配
通过预配置的主机标识符并跟踪共享内存中的最新标识符,能够实现跨机器快速并发生成标识符,达到每秒十亿的理论极限。
方法很简单:取当前时间,去掉低两位数字并添加hostId,只要这高于最后一个标识符,就可以了。
如果机器发生故障,最后一个标识符的信息丢失,假设重新启动服务的时间足以确保没有重叠。
如果服务失败了,但机器没有,信息就会被保留。
 
下面代码使用了开源库Chronicle Bytes支持的共享内存中的 MappedFile :

@Override
public long currentTimeNanos() throws IllegalStateException {
 long time provider.currentTimeNanos();
 long timeo bytes.readVolatileLong(LAST_TIME);
 long timeN = time - time % HOST_IDS + hostid;
  if (timeN > timeo && bytes.compareAndSwapLong(LAST_TIME, timeo, timeN))
     return timeN;
 return currentTimeNanosLoop();


使用 JMH测试:

On an i9-10980HK

Benchmark                                                Mode  Cnt   Score   Error  Units

DistributedUniqueTimeProviderBenchmark.currentTimeNanos  avgt   25  37.395 ± 0.391  ns/op

DistributedUniqueTimeProviderBenchmark.randomUUID        avgt   25 207.709 ± 1.586  ns/op

On a Ryzen 9 5950X
Benchmark                                                Mode  Cnt    Score   Error  Units
DistributedUniqueTimeProviderBenchmark.currentTimeNanos  avgt   25   43.557 ± 0.801  ns/op
DistributedUniqueTimeProviderBenchmark.randomUUID        avgt   25  265.285 ± 2.690  ns/op


这种方法比UUID快6倍
 

1