【初衷】
由于系统改造,之前的单应用改成了分布式应用,但是系统底层在搭建的时候部分关联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 位有符号整数,范围从 -2147483648 到 2147483647。
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=1024throw 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++) {// 生成并解析IDint 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}}
}