【初衷】

由于系统改造,之前的单应用改成了分布式应用,但是系统底层在搭建的时候部分关联id定义为了int类型,导致分布式id生成的long类型无法插入到int中,且由于是多系统部署,为了把损失降到最低,故此决定把分布式生成long类类型主键id的方法改成生成int类型然后强转为long类型,多次尝试下来发现下面这种最稳定和好用,当然目前是为了验收所以在影响最小的前提下做了这改动,等验收完成后会修改表结构,然后继续用long,毕竟应付是暂时了,系统长久稳定才是程序员的最终追求

【评价】

1. 重复性分析

在这种方法中,生成的 ID 由三个部分组成:

  • 时间戳部分:21 位
  • 序列号部分:10 位
  • 随机数部分:12 位
时间戳部分:
  • 时间戳从 EPOCH (2024-06-01 00:00:00)开始计算,单位是秒。因为时间戳是基于当前秒数来生成的,并且每秒生成的 ID 都包含时间戳部分,这部分的唯一性基本上没有问题。
序列号部分:
  • 序列号在每秒内从 0 开始递增,最大值为 1023 (即 2^10 - 1),因此每秒最多生成 1024 个 ID。每秒钟会重置序列号,保证了在同一秒内生成的 ID 是唯一的。
  • 可能的问题:如果每秒的 ID 生成数量超过 1024,就会发生序列号溢出,抛出异常 Sequence overflow in current second。所以,如果在每秒内生成 ID 的速度很快,超过了 1024 次,会出现 ID 重复的情况。
随机数部分:
  • 随机数部分由 12 位构成,范围是 0 到 4095,这保证了在每次生成 ID 时,随机数部分的值是变化的,从而增加了唯一性。
  • 在同一秒钟内,即使序列号部分已经递增到了最大值 1023,随机数部分仍然能提供额外的变化,减少了重复的可能性。
总结:
  • 在正常情况下,每秒内最多生成 1024 个 ID,如果每秒生成的数量超过了 1024,会发生序列号溢出。因此,ID 的重复性主要受限于每秒生成的 ID 数量。
  • ID 会重复的条件:如果每秒生成的 ID 数量超过 1024,则会触发序列号溢出,导致重复。

2. 负数分析

Java 中的 int 类型是 32 位有符号整数,范围从 -21474836482147483647

ID 的构成:
  • 时间戳部分:21 位左移后仍然是正数,因为时间戳部分的位数不可能超出 31 位,所以不会导致符号位的变化。
  • 序列号部分:10 位左移后也不会影响符号位,因为最多左移 12 位,所以序列号部分也不会导致符号位变化。
  • 随机数部分:12 位本身不会影响符号位。
因此,生成的 ID 永远是正数。具体分析:
  • 左移操作:左移操作会让数值的高位变为 0,而 int 类型最高位是符号位。如果数据位数没有超出 31 位,则不会引起负数。
  • 结果:通过组合后的 ID 的最高位始终为 0,确保了生成的 ID 永远是正数。

3. 结论

  • 重复性:在正常使用情况下,ID 不会重复。唯一性问题仅会在每秒内生成超过 1024 次 ID 时才会出现。
  • 负数:生成的 ID 永远不会是负数,因为经过左移和组合操作后,最高位始终是 0

【注意】 每秒内生成超过 1024 次 ID 时才会出现

import java.util.concurrent.atomic.AtomicInteger;
import java.util.Random;

public final class HybridGenerator {
    // 调整EPOCH为更近的时间点,避免时间戳部分溢出
    private static final long EPOCH = 1717200000L; // 2024-06-01 00:00:00 (秒)
    private static final AtomicInteger sequence = new AtomicInteger(0);
    private static volatile long lastSecond = -1L;
    private static final Random random = new Random();

    /**
     * 生成唯一且非负的int型ID
     * 结构:21位时间戳 + 10位序列号 + 12位随机数(保证不重复且不溢出)
     */
    public static synchronized int nextId() {
        long currentSecond = System.currentTimeMillis() / 1000L;

        // 时间戳部分(限制在21位,避免溢出)
        long timestampPart = (currentSecond - EPOCH) << 21; // 左移21位

        // 序列号部分(10位,范围0-1023)
        if (currentSecond != lastSecond) {
            sequence.set(0);
            lastSecond = currentSecond;
        }
        int currentSequence = sequence.incrementAndGet();
        if (currentSequence >= 1024) { // 2^10=1024
            throw new RuntimeException("Sequence overflow in current second");
        }
        int sequencePart = currentSequence << 12; // 左移12位

        // 随机数部分(12位,范围0-4095)
        int randomPart = random.nextInt(4096); // 0~4095

        // 组合并确保非负(最高位始终为0)
        return (int) (timestampPart | sequencePart | randomPart);
    }

    /**
     * 解析时间戳
     */
    public static long parseTimestamp(int id) {
        return EPOCH + (id >>> 21); // 无符号右移21位
    }

    /**
     * 解析序列号
     */
    public static int parseSequence(int id) {
        return (id >>> 12) & 0x3FF; // 右移12位后取10位
    }

    /**
     * 解析随机数部分
     */
    public static int parseRandomPart(int id) {
        return id & 0xFFF; // 取最后12位
    }

    // 测试代码
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            // 生成并解析ID
            int id = nextId();
            System.out.println("id = " + id);
//            System.out.println("Generated ID: " + id + " (Hex: " + Integer.toHexString(id) + ")");
//            System.out.println("Timestamp: " + parseTimestamp(id));
//            System.out.println("Sequence: " + parseSequence(id));
//            System.out.println("Random: " + parseRandomPart(id));
//
//            // 验证无符号性质
//            System.out.println("Is negative? " + (id < 0)); // 始终输出false
        }
    }
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐