基于时间戳的唯一标识符的轻量级跟踪方法

程序中的唯一标识符对于跟踪非常有用。当这些 id 包含高分辨率时间戳时,它们会更加有用。

唯一标识符不仅记录事件的时间,而且是唯一可以帮助跟踪通过系统的事件。

这种独特的时间戳根据实现方式的不一样,所需要的成本会比较高。

接下来我们探讨了一种轻量级的方法,可以在我们研发中生成一个独特的、单调递增的纳秒分辨率时间戳。

唯一标识符的用途

唯一标识符可用于与一条信息相关联,以便以后可以明确地引用信息。它可以是事件、请求、订单 ID 或客户 ID。

它们的业务可以用作数据库或键/值存储中的主键,以便后面检索辨识该信息。

生成这些标识符的挑战之一是在不增加成本的同时避免创建重复项。

我们可以记录在数据库中创建的每个标识符,但是我们要添加更多标识符时,这会使用 O(n) 存储。

您可以生成一个随机标识符,例如不太可能重复的 UUID,但是,这会创建比较大 id(不要看只是一个字符串,当量大时,就非常庞大了),否则不包含任何信息。例如,UUID 可能看起来像
**
d85686f5-7a53-4682-9177-0b64037af336**

此 UUID 可以存储为 16 个字节,但通常存储为占用 40 个字节内存的对象。

使用 256 位可降低重复标识符的风险,但会使内存增加一倍。

时间戳作为唯一标识符

使用时间戳有两个好处。您不需要存储太多信息,因为时钟是驱动程序的。您只需要检查两个不同时间的线程,缺点是在重新启动时丢失,例如,时钟时间应该已经足够长,仍然不会得到重复的时间戳。

这样的标识符也更容易阅读,并提供对跟踪有用的附加信息。基于时间戳的唯一标识符可能类似于**
2021-12-20T23:30:51.8453925**

这个时间戳可以存储在 LocalDateTime 对象中,可以存储为 8 个字节长。

MappedUniqueTimeProvider 代码

这是GitHub 上提供的MappedUniqueTimeProvider的精简

/** * Timestamps are unique across threads/processes on a single machine. */
public enum MappedUniqueTimeProvider implements TimeProvider {
    INSTANCE;

    private final Bytes bytes;
    private TimeProvider provider = SystemTimeProvider.INSTANCE;

    MappedUniqueTimeProvider() {
        String user = System.getProperty("user.name", "unknown");
        MappedFile file = MappedFile.mappedFile(OS.TMP + "/.time-stamp." + user + ".dat", OS.pageSize(), 0);
        bytes = file.acquireBytesForWrite(mumtp, 0);
    }

    @Override
    public long currentTimeNanos() throws IllegalStateException {
        long time = provider.currentTimeNanos(), time5 = time >>> 5;
        long time0 = bytes.readVolatileLong(LAST_TIME), timeNanos5 = time0 >>> 5;

        if (time5 > timeNanos5 && bytes.compareAndSwapLong(LAST_TIME, time0, time))
            return time;

        while (true) {
            time0 = bytes.readVolatileLong(LAST_TIME);
            long next = (time0 + 0x20) & ~0x1f;
            if (bytes.compareAndSwapLong(LAST_TIME, time0, next))
                return next;
            Jvm.nanoPause();
        }
    }
}

以下技术已用于确保时间戳的唯一性和效率

内存共享

TimeProvider 使用共享内存来确保纳秒分辨率时间是唯一的。内存映射文件以线程安全的方式访问,以确保时间戳单调递增。Chronicle Bytes有一个库支持对内存映射文件的线程安全访问。

读取内存映射文件中的值并尝试在循环中更新。CAS 或compare-and-swap操作是原子的,并检查先前的值没有被另一个线程更改。当然,这是在同一台服务上的一个线程上操作。

存储一个纳秒的时间戳

我们使用原始的 long 来存储时间戳可以提高效率,但这可更难使用,我们支持print和解析称为
NanoTimestampLongConverter的长时间戳,我们也将这些时间戳解析并隐式呈现为文本使其更容易打印、调试和创建单元测试。

public class Event extends SelfDescribingMarshallable {
    @LongConversion(NanoTimestampLongConverter.class)    long time;
}
Event e = new Event();
e.time = CLOCK.currentTimeNanos();
String str = e.toString();
Event e2 = Marshallable.fromString(str);
System.out.println(e2);
Prints
!net.openhft.chronicle.wire.Event {
  time: 2021-12-20T23:30:51.8453925
}

由于纳秒时间戳是一种高分辨率格式,它只会持续到 2262 年作为有符号长整数或 2554 年,值溢出之前,可以假设它是无符号长整数。

我们已经将时间戳中的额外位置用于其他目的,例如存储主机标识符或源 ID。出于这个原因,我们还确保时间戳对于 32 ns 的倍数是唯一的,我们如果愿意,可以将低 5 位用于其他目的。

效果

在正常操作下,在服务器上获得唯一的纳秒时间戳需要不到 50 ns。在繁重的多线程负载下,可能需要几百纳秒。

MappedUniqueTimeProvider 应用程序可以维持超过 3000 万/秒的生成。

可重启性

只要时间不倒退,这种策略就可以丢失所有状态,但仍能确保仅从时钟上的唯一性。如果时钟时间确实倒退了一个小时,那么状态将确保没有重复,但是,在时钟赶上之前,时间戳不会与时钟匹配。

结论

可以有一个轻量级的、唯一的标识符生成器来保存纳秒时间戳。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MobotStone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值