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

30次阅读

共计 2634 个字符,预计需要花费 7 分钟才能阅读完成。

程序中的惟一标识符对于跟踪十分有用。当这些 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