【问题标题】:12 digit unique distributed random number generator12位唯一分布式随机数发生器
【发布时间】:2020-04-22 14:22:25
【问题描述】:

我将 sonyflake 移植到 Java,它运行良好。但是,我希望生成 12 位唯一数字,而不是生成 8 位数字。原始端口使用 16 位 machineId。因为我们至少有 2 个数据中心,但不限于,我为数据中心添加了 8 位 - 使用 IP 地址的第二个八位字节。我调整了位长的所有设置,无法生成 12 位数字。是否有受 sonyflake 或 Twitters Snowflake 启发的算法来生成使用 16 位 machineId 和 8 位 dataCenterId 的唯一 12 位数字?

注意:由于公司政策,我不能在此处发布我的原始 Java 端口。

编辑:这是我想出的。但是,它不是生成 12 位十进制数字,而是生成 10 位或 11 位数字。我可以对其进行哪些更改以使其始终返回 12 位十进制数?我知道我需要更改sequence 并重新计算时间。但是,我目前想专注于生成 12 位十进制数。

public class Demo {

    /// 17 time + 4 dc + 10 machine + 8 sequence

    private static final int BIT_LEN_TIME = 17;
    private static final long BIT_WORKER_ID = 10L;
    private static final long BIT_LEN_DC = 4L;
    private static final long BIT_LEN_SEQUENCE = 8L;

    private static final int MAX_WORKER_ID = (int) (Math.pow(2, BIT_WORKER_ID) - 1);
    private static final long MAX_SEQUENCE = (int) (Math.pow(2, BIT_LEN_SEQUENCE) - 1);

    private static final double FLAKE_TIME_UNIT = 1e7; // nsec, i.e. 10 msec
    private static final double LEN_LIMIT = 1e11;
    private static final int START_SEQ = 0;

    private final ReentrantLock mutex = new ReentrantLock();

    private final Instant startInstant;
    private final long startTime;
    private final long dc;
    private long sequence;
    private long lastElapsedTime;
    private long worker;

    public Demo(Instant startInstant) {
        Objects.requireNonNull(startInstant, "startInstant cannot be null");
        if (startInstant.isBefore(Instant.EPOCH) || startInstant.isAfter(Instant.now())) {
            throw new Exception("Base time should be after UNIX EPOCH, or before current time.");
        }

        this.startInstant = startInstant;
        this.startTime = this.toEverestFlakeTime(startInstant);
        this.sequence = START_SEQ;
        this.dc = this.msb(this.getDcId()); // 4 bits at most
        this.worker = this.workerId() & ((1 << BIT_WORKER_ID) - 1); // 10 bits at most
    }

    public long next() {
        long currentElapsedTime = this.currentElapsedTime(this.startTime);

        mutex.lock();
        long time = currentElapsedTime & ((1 << BIT_LEN_TIME) - 1); // 17 bits at most
        if (this.sequence == MAX_SEQUENCE) {
            this.sequence = START_SEQ;
            System.out.println("time = " + time);
            sleepMicro(currentElapsedTime - this.lastElapsedTime);
            time = this.currentElapsedTime(this.startTime) & ((1 << BIT_LEN_TIME) - 1);
            System.out.println("time = " + time);
        } else {
            // less than 15000
            if((currentElapsedTime - this.lastElapsedTime) < 0x3a98) {
                sleepMicro(currentElapsedTime - this.lastElapsedTime);
                time = this.currentElapsedTime(this.startTime) & ((1 << BIT_LEN_TIME) - 1);
            }
            this.sequence += (START_SEQ + 1) & MAX_SEQUENCE;
        }

        long id = (time << BIT_LEN_TIME) |
                (worker << BIT_WORKER_ID) |
                (dc << BIT_LEN_DC) |
                (sequence << BIT_LEN_SEQUENCE);
        id += LEN_LIMIT;
        this.lastElapsedTime = currentElapsedTime;
        mutex.unlock();

        return id;
    }

    private void sleepNano(long sleepTime) {
        try {
            System.out.println("nano sleeping for: " + sleepTime);
            TimeUnit.NANOSECONDS.sleep(sleepTime);
        } catch (Exception e) {
            //
        }
    }

    private void sleepMicro(long sleepTime) {
        try {
            System.out.println("micro sleeping for: " + sleepTime);
            TimeUnit.MICROSECONDS.sleep(sleepTime/100);
        } catch (Exception e) {
            //
        }
    }

    private long toEverestFlakeTime(Instant startInstant) {
        return unixNano(startInstant);
    }

    private long unixNano(Instant startInstant) {
        return NanoClock.systemUTC().nanos(startInstant);
    }

    private long currentElapsedTime(long startTime) {
        return this.toEverestFlakeTime(NanoClock.systemUTC().instant()) - startTime;
    }

    private long msb(long n) {
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        n >>>= 1;
        n += 1;
        return n;
    }

    private int workerId() {
        return new SecureRandom().nextInt(MAX_WORKER_ID);
    }

    private int getDcId() {
        try {
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress("google.com", 80));
            byte[] a = socket.getLocalAddress().getAddress();
            socket.close();
            return Byte.toUnsignedInt(a[1]);
        } catch (Exception e) {
            String message = "Failed to process machine id.";
            throw new EverestFlakeException(message, e);
        }
    }
}

【问题讨论】:

  • 您介意分享您的解决方案的伪代码吗?

标签: java random


【解决方案1】:

如果您的意思是 12 位十进制数字,那么您可以使用最多 39 位的数字(40 位可以表示 12 位数字之外的 13 位数字)。

如果您将 16 位用于机器 ID,8 位用于数据中心 ID,则该机器 ID 的唯一部分仅剩下 15 位(因此每台机器只有 32768 个唯一编号。)编号,您可以选择顺序分配编号而不是随机分配。

如果您的意思是 12 个十六进制(base-16)位,那么情况会大大改善:16 位组成 4 位,8 位组成另外两个,剩下 6 个 base-16 位ID 的唯一部分,或 16,777,216 个不同的数字(24 位)。有了这么多号码,您有多种不同的选择让每台机器分配这些号码。您可以按顺序执行,也可以随机执行(使用java.security.SecureRandom,而不是java.util.Random),或使用分辨率为 10 毫秒的时间戳,如 Sonyflake。


看来您的问题与其说是关于如何生成 12 位唯一 ID,不如说是关于如何格式化数字以恰好适合 12 位数字。那么你有两个选择。假设您有一个 39 位整数 x(小于 239,因此小于 1012)。

  • 如果您可以接受数字中的前导零,则执行以下操作将x 格式化为 12 位数字:String.format("%012d", x)

  • 如果您不能接受数字中的前导零,则将 100000000000 (1011) 添加到 x。由于x 小于 239,即小于 900000000000,这将产生一个 12 位数字。


您正在随机生成工作人员 ID。一般来说,随机数本身不足以确保唯一性。您需要某种方法来检查您生成的每个工作人员 ID 的唯一性。这样做后,每个工作人员/数据中心对都将是唯一的。因此,每台机器只需要生成一个机器唯一编号,在您的情况下将是 25 位长。有几种方法可以做到这一点:

  • 最简单的方法是生成一个随机数或基于时间的数字(在您的示例中使用所有 25 位)并使用哈希集(例如,java.util.HashSet)检查数字的唯一性。如果号码已经生成,请使用新号码重试。 (使用位集(例如java.util.BitSet)或压缩位图(例如“咆哮位图”)可能更节省内存。)

  • 另一种方法是使用哈希表,其中随机/基于时间的数字作为键,序列 ID 作为值。当需要生成唯一编号时,请执行以下操作:

    1. 生成X,一个随机数或基于时间的数字。
    2. 检查是否在哈希表中找到等于X的键。
    3. 如果key不存在,则在表中新建条目,key为X,value为0。唯一编号为X,序列号为0。停止。
    4. 如果键存在,并且与该键关联的值小于 255,则将该值加 1。唯一编号是X 和该值的序列号。停下来。
    5. 如果键存在,并且与该键关联的值是 255 或更大,请转到步骤 1。

【讨论】:

  • 感谢您的回复。我添加了一些我想出的代码。介意看看吗?它目前正在生成 10 到 11 位的十进制数字。我应该做哪些更改以使其“始终”生成 12 位十进制数?不幸的是,我无法生成十六进制数字。
  • 也许,我在解释问题时并不清楚。道歉。虽然您的方法有效,但我实际上希望生成由 Sonyflake “启发”的唯一 12 位十进制数字。我还需要将生成的数字“分解”为其组成部分。使用你的方法,我不确定这是否可能。
  • 您仍然可以根据需要“分解”数字,即使在应用上述任一方法后也是如此。例如,如果您添加 100000000000 以使数字长为 12 位,您可以稍后从该数字中减去 100000000000 并将结果“分解”成各个部分。
  • 是的,这会起作用。感谢您的澄清。但是,我尝试生成 1000 个数字,并尝试运行一些测试。对于 1000 个数字,它生成了 999 个唯一数字,对于 10000 个数字,它生成了 9986 个唯一数字。你看到我可以做任何调整来确保唯一性吗?非常感谢。我稍微更改了代码。我在启动过程中只初始化了一次workerIddcId
猜你喜欢
  • 1970-01-01
  • 2012-04-14
  • 1970-01-01
  • 1970-01-01
  • 2013-03-13
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多