【问题标题】:How to generate incremental identifier in java如何在java中生成增量标识符
【发布时间】:2012-10-16 09:06:44
【问题描述】:

我有要求我不断收到需要写入文件的消息。每次收到新消息时,都需要将其写入单独的文件中。我想要的是生成一个唯一标识符以用作文件名。我也想保留消息的顺序。我的意思是,作为文件名生成的标识符应该始终是增量的。

我使用 UUID.randomUUID() 来生成文件名,但这种方法的问题是 UUID 只保证标识符的随机性,而不是增量。结果我失去了文件的顺序(我希望首先生成的文件应该出现在列表的第一位)。

已知方法

  1. 可以使用 System.currentTimeMillis(),但我可以同时接收多条消息。

2.另一种方法是实现静态长值并在要创建文件时将其递增并将长值用作文件名。但我不确定这种方法。此外,这似乎不是解决我的问题的正确方法。我认为可能有比这个更好的解决方案。

如果有人能建议我更好地解决这个问题,将不胜感激。

【问题讨论】:

  • 我每次只添加一个到基数大于 10 的 BigInteger
  • 你不能在 uuid 前面加上时间戳吗?因此,您既可以保留顺序(时间戳),又可以在 id 中具有随机性
  • @AJcodez :你能用一个例子解释一下上述方法吗?它可以帮助我更好地理解。
  • @ph。 : 你说的好像还不错。但我唯一担心的是,如果你在前面加上时间戳,id 会太长。
  • @Ankur 我不确定文件名是否为 50 字符真的是个问题

标签: java


【解决方案1】:

如果您希望您的 id 值即使在服务器重新启动之间也均匀上升,那么您必须基于系统时间或使用一些精心设计的稳健逻辑来保留最后使用的 ID。请注意,单独实现鲁棒性并不难,但以高性能和可扩展的方式实现它却是。

如果您还需要 id 在冗余服务器集群中的多个节点之间是唯一的,那么您需要更复杂的逻辑,这肯定涉及所有盒子同步访问的持久存储。当然,要实现这一性能更难。

我能看到的最佳选择是拥有一个相当长的 ID,以便为这些部分留出空间:

  1. System.currentTimeMillis 用于长期唯一性(跨重启);
  2. System.nanotime 更细粒度;
  3. 每个服务器节点的唯一 ID(以特定于平台的方式确定)。

该方法仍然需要记住最后生成的值并在出现重复时重试。不过,它不必重试太多次,直到下一个 nanoTime 时钟滴答声——它甚至可以忙着等待。

没有第3点的代码草图(单节点实现):

private static long lastNanos;
public static synchronized String uniqueId() {
  for (;/*ever*/;) {
    final long n = System.nanoTime();
    if (n == lastNanos) continue;
    lastNanos = n;
    return "" + System.currentTimeMillis() + n;
  }
}

【讨论】:

  • 我已经删除了我公认的片状答案,并为你的答案投了赞成票。请尽量不要在未来的 cmets 中对他人如此咄咄逼人。
  • @AdrianRegan 抱歉,我没有注意到这种激进的语气(我猜有时它会滑倒)。直接语气与攻击性很容易混淆。作为记录,我没有否决您的解决方案:)
  • 没问题。但你这样做是对的。我需要咖啡... :-)
  • 哦,我以为是给我的 .. 不管怎样,伙计们都很冷静 :)
【解决方案2】:

好的,请举手。我的最后一个答案相当不稳定,我已将其删除。

与网站的精神保持一致,我想尝试不同的策略。

如果您说您将这些消息保存在单个文件中,那么您可以尝试根据文件大小创建一个唯一的 Id?

在将消息写入文件之前,它的 id 可能是文件的当前大小。

如果这些消息需要在多个文件中是唯一的,您可以添加文件名 + 大小作为 id。

我将把同步的烫手山芋留到另一天。但是您可以将所有这些都包装在一个同步对象中,以跟踪事物。

另外,我假设写入文件的任何消息将来都不会被删除。

附加说明: 您可以创建一个消息处理对象,该对象在构造时(或通过 create 方法)打开文件。 该对象将获取文件的初始大小,并将其用作唯一 ID。 随着每条消息的添加(以同步方式),id 会随着消息的大小而增加。 这将解决性能问题。如果多个 JVM/Node 访问同一个文件,将无法正常工作。

骨架理念:

public class MessageSink {
    private long id = 0;

    public MessageSink(String filename) {
       id = ... get file size ..
    }

    public synchronized addMessage(Message msg) {
       msg.setId(id);
       .. write to file + flush ..
       .. or add to stack of messages that need to be written to file
       .. at a later stage.
       id = id + msg.getSize();
    }

    public void flushMessages() {
       .. open file
       .. for each message in stack write ...
       .. flush and close file
    }
}

【讨论】:

  • 这可以工作(而且是一个非常聪明的想法!),但我看到的问题是文件必须一直保持打开状态,并且在确定大小时可能存在特定于平台的问题当前打开以供写入的文件。不断打开和关闭文件可以解决这个问题,但可能会导致性能下降。
  • 我将始终将固定数量的消息保存在一个文件中。此外,消息也可以具有相同的长度,如果有的话,只需稍作微小的变化。这些存储的消息不是永久的。处理完文件后,我将清除它们。
  • 我认为它必须发生在“事务性”上下文中,即打开、获取大小、写入、关闭、刷新。它会很慢,但由于@Ankur 已经在写入文件,因此可以将其合并到此功能中。我不知道有多少消息到达或速率。但如果它很慢而且时间不紧迫??
  • @AdrianRegan :消息可以以任意速率到达。我也可能遇到一堆消息到达同一个实例或在很短的时间内到达的情况,比如几毫秒左右。
  • @Ankur 然后您可以将它们排队到 MessageSink 类中,以便稍后刷新到文件中。滚动 id 只是当前 id + 消息的大小。如果同步正确,那么应该没问题。
【解决方案3】:

我有同样的要求,并找到了合适的解决方案。 Twitter Snowflake 使用一种简单的算法来生成可排序的 64 位(长)ID。 Snowflake 是在 Scala 上编写的,但方法很简单,可以很容易地在 Java 代码中使用。

id 由以下部分组成: timestamp - 41 位(毫秒精度/自定义纪元给我们 69 年); 机器 id - 10 位(MAC 地址可以用作硬件 id); 序列号 - 12 位 - 每台机器每 4096 次翻转一次(具有保护以避免在同一毫秒内翻转)

公式如下:((timestamp - customEpoch) << timestampShift) | (machineId << machineIdShift) | sequenceNumber;

每个组件的移位取决于它在 ID 中的位位置。

详细说明和源码可以在github上找到:

Twitter Snowflake

Basic Java implementation of the Snowflake algorithm

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    • 2012-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-27
    相关资源
    最近更新 更多