生成19位bigint/long类型的不重复id

该项目描述了如何在不重复的情况下生成具有高查询效率的19位bigint(长整型)ID,用于作为全局唯一标识符。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目中经常会用到id,一个项目中偶尔会使用到全局id,要求不重复且具有一定的查询效率,所以写了一个19位的不重复id生成器


public class IdWorker {
	/**
	 * 一启动就创建一个worker,如果有多台机器,则workerID和datacenterID可以从配置文件中读取
	 */
	private static IdWorker worker = new IdWorker(1, 1, 1);

	/**
	 * 生成一个ID
	 * 
	 * @return
	 */
	public static long getId() {
		return worker.nextId();
	}

	private long workerId;
	private long datacenterId;
	private long sequence;

	private IdWorker(long workerId, long datacenterId, long sequence) {
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
		}

		if (datacenterId > maxDatacenterId || datacenterId < 0) {
			throw new IllegalArgumentException(
					String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
		}

		System.out.printf(
				"worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d \r\n",
				timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

		this.workerId = workerId;
		this.datacenterId = datacenterId;
		this.sequence = sequence;
	}

	private long twepoch = 1288834974657L;

	private long workerIdBits = 5L;
	private long datacenterIdBits = 5L;
	private long maxWorkerId = -1L ^ (-1L << workerIdBits);
	private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
	private long sequenceBits = 12L;

	private long workerIdShift = sequenceBits;
	private long datacenterIdShift = sequenceBits + workerIdBits;
	private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	private long sequenceMask = -1L ^ (-1L << sequenceBits);

	private long lastTimestamp = -1L;

	private synchronized long nextId() {
		long timestamp = timeGen();

		if (timestamp < lastTimestamp) {
			System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
			throw new RuntimeException(String.format(
					"Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
		}

		if (lastTimestamp == timestamp) {
			sequence = (sequence + 1) & sequenceMask;
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			sequence = 0;
		}

		lastTimestamp = timestamp;
		return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
				| (workerId << workerIdShift) | sequence;
	}

	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	private long timeGen() {
		return System.currentTimeMillis();
	}

	static Map<Long, Long> kvs = Collections.synchronizedMap(new HashMap<Long, Long>());

	// ---------------测试---------------
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			Thread t = new Thread(new Runnable() {
				public void run() {
					// System.out.println( Thread.currentThread().getId());
					for (int i = 0; i < 1000; i++) {
						long id = IdWorker.getId();
						System.out.println(id);
						if (!kvs.containsKey(id)) {
							kvs.put(id, id);
							// System.out.println(id);
						} else {
							System.out.println("重复的ID:" + id);
						}
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
			});
			t.start();
		}
	}
}

 

<think>好的,我现在需要帮助用户解决如何在Java中生成10和12的全局唯一ID的问题。首先,我需要回顾之前用户的问题历史,了解他们之前询问过除时间戳外的全局唯一ID生成方法,现在他们可能希望更具体地生成特定长度的数字ID。 用户之前的问题是关于Java中全局唯一ID的其他方法,比如UUID、Snowflake、数据库序列等。现在他们需要的是生成10和12数字ID,这可能意味着他们需要的是更短、更紧凑的数字形式,而是像UUID那样的字符串或者更长的Snowflake ID。 首先,我需要确定生成数字的全局唯一ID的方法。常见的数字ID生成方式包括数据库自增、Snowflake算法、Redis的自增操作,以及一些分段批量生成的方案,比如美团Leaf。过,这些方法生成ID数可能符合用户的要求。例如,标准的Snowflake生成的是64的长整型,转换成十进制通常是18左右,显然超过了用户需要的10或12。 因此,我需要考虑如何调整这些方法,或者采用其他方法来缩短数。可能的思路包括限制时间戳的范围、减少机器ID和序列号的数,或者采用哈希截取并处理冲突。但需要注意,缩短数可能会增加冲突的风险,因此需要权衡。 对于10数字ID,范围是0到9999999999,即大约100亿个唯一值。而12则是到999999999999,即约1万亿个。系统需要确保在ID的生命周期内会耗尽,并且生成速度足够快。 首先,考虑数据库自增。如果使用数据库的自增主键,可以通过设置起始值和步长来控制数。例如,从1亿开始自增,可以生成10数字,但需要考虑单点故障和分库分表的情况。可能需要分片策略,比如每个实例生成同范围的ID,但需要确保重叠。 其次,Snowflake算法的变种。调整Snowflake的时间戳、机器ID和序列号的数分配。例如,对于10ID,可以分配时间戳为24(约16天),机器ID5(32台机器),序列号5(每个节点每秒生成32个ID)。但这样的时间范围可能太短,容易导致重复。或者采用更短的时间戳,比如使用相对时间,从某个起点开始计算。 另外,可以考虑使用Redis的自增命令,通过设置合适的初始值和自增步长,比如起始值为1000000000,这样自增生成ID都是10的。但需要确保Redis的高可用性,避免单点故障。 还有一种方法是使用哈希函数(如MD5或SHA-1)生成较长的哈希值,然后截取部分并转换为数字,再取模得到所需数。但这样可能会有冲突,需要额外处理,比如重试或使用布隆过滤器检测冲突。 接下来,针对10和12的具体实现,我需要考虑同方法的可行性和潜在问题。例如,对于10ID,使用数据库分片,每个实例生成同范围的ID,比如实例1生成1-1000000000,实例2生成1000000001-2000000000,依此类推。但需要协调分片策略,避免重复。 或者,使用时间戳的一部分,比如取当前时间的秒数或毫秒数的后几,结合机器ID和序列号,组合成较短的ID。例如,取毫秒数的后5,机器ID3,序列号2,组合成10数字。但需要确保时间戳部分会在短时间内重复,特别是在高并发场景下。 对于12ID,同样的方法可以有更多的数分配,时间戳部分可以更长,比如30(约34年),机器ID6(64台机器),序列号6(每个节点每秒生成64个ID),总和为30+6+6=42,这样转换为十进制可能超过12,所以需要调整。可能需要更紧凑的分配,例如时间戳用更小的粒度,或者采用同的编码方式,如Base62而是十进制,但用户要求的是纯数字,所以只能使用十进制。 可能的另一种方法是使用美团的Leaf方案,通过预分配号段的方式生成ID。例如,服务启动时从数据库获取一个号段范围,如1000000000-1000009999,这样每个实例可以生成1000个10ID,之后重新获取下一个号段。这需要数据库来管理号段的分配,并处理实例重启或故障的情况。 还需要考虑ID生成速度和系统的吞吐量。例如,如果每秒需要生成大量的ID,那么像Snowflake这样的算法可能更合适,而Leaf方案则需要足够的号段缓存来应对高并发。 此外,必须处理时钟回拨的问题,尤其是在使用基于时间的算法时。可能需要使用NTP同步时钟,或者在检测到回拨时等待或抛出异常。 最后,要确保生成ID仅是唯一的,还要尽可能避免猜测和规律性,尤其是在安全敏感的场合。过,用户可能更关注的是唯一性和性能,而是安全性。 总结可能的实现方案: 1. 改造Snowflake算法,调整各部分的数分配以适应10或12。 2. 使用数据库分片或自增步长生成特定范围的ID。 3. 利用Redis的自增操作,设置合适的起始值和步长。 4. 预分配号段,如Leaf方案,管理号段的分配和缓存。 5. 结合时间戳和随机数,通过哈希和截取生成较短的数字ID,处理可能的冲突。 需要为每种方案提供代码示例或具体步骤,并指出优缺点,帮助用户根据实际场景选择最合适的方法。</think>在Java中生成10/12数字全局唯一ID需要根据具体场景选择同实现方案,以下分步骤说明可行方法及实现要点: --- ### **一、10数字ID生成方案(范围:1-9,999,999,999)** #### **1. 分段数据库自增法** ```java // 数据库设计(MySQL示例) CREATE TABLE id_generator ( biz_tag VARCHAR(32) PRIMARY KEY, -- 业务标识 max_id BIGINT NOT NULL, -- 当前最大ID step INT NOT NULL -- 号段长度 ); // Java代码(使用MyBatis获取) public synchronized Long generateId(String bizTag) { IdRange range = dao.selectForUpdate(bizTag); // 加锁查询 long current = range.getCurrent(); if (current >= range.getMax()) { // 号段用完时申请新号段 dao.updateMaxId(bizTag, range.getMax() + range.getStep()); current = range.getMax(); } return ++current; } ``` **核心参数**: - 初始`max_id=1_000_000_000`(10起始值) - `step=1000`(每次取1000个ID) **优点**:完全可控、可扩展 **缺点**:需要数据库维护 --- #### **2. 压缩版Snowflake算法** ```java public class CompactSnowflake { private final long workerId; // 5(0-31) private final long datacenterId; // 5(0-31) private long sequence = 0L; private long lastTimestamp = -1L; public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 0x1F; // 5序列号(0-31) if (sequence == 0) timestamp = tilNextMillis(lastTimestamp); } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - 1609459200000L) << 20) // 从2021-01-01开始的毫秒数 | (datacenterId << 15) | (workerId << 10) | sequence; } } ``` **分配**: - 时间差:30(约34年有效期) - 数据中心+机器:5+5 - 序列号:5 **输出处理**:将生成的64数值转换为十进制后截取最后10 **注意**:需定期校准基准时间戳 --- ### **二、12数字ID生成方案(范围:1-999,999,999,999)** #### **1. Redis原子递增法** ```java Jedis jedis = new Jedis("redis-server"); // 初始化:jedis.set("order_id", "100000000000"); public String generate12DigitId() { Long id = jedis.incr("order_id"); if (id > 999_999_999_999L) { throw new RuntimeException("ID溢出"); } return String.format("%012d", id); // 补零到12 } ``` **关键配置**: - 初始值设置为`100_000_000_000`(保证12起点) - 启用Redis持久化防止数据丢失 --- #### **2. 时间戳+随机数组合法** ```java public class TimestampRandomGenerator { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHH"); private static final AtomicInteger counter = new AtomicInteger(0); public static String generate() { String timePart = sdf.format(new Date()); // 8(年月日小时) String randomPart = String.format("%04d", ThreadLocalRandom.current().nextInt(10000)); // 4随机数 return timePart + randomPart; // 合计12 } } ``` **冲突处理**: - 添加自增序列:`counter.getAndIncrement() % 10000` - 每小时重置计数器 --- ### **三、方案对比选型** | 方案 | 10适用性 | 12适用性 | 并发能力 | 实现复杂度 | |---------------------|-----------|-----------|---------|----------| | 分段数据库自增 | ★★★★☆ | ★★★★☆ | 中 | 中 | | 压缩Snowflake | ★★★☆☆ | ★★★★☆ | 高 | 高 | | Redis原子递增 | ★★☆☆☆ | ★★★★☆ | 高 | 低 | | 时间戳+随机数 | ★☆☆☆☆ | ★★★☆☆ | 低 | 低 | --- ### **四、必须注意的临界问题** 1. **ID耗尽风险**: - 10ID:每秒生成1万ID时,约需31年耗尽 - 12ID:同等速率下可持续3,171年 2. **时钟回拨处理**: ```java protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { Thread.sleep(10); timestamp = timeGen(); } return timestamp; } ``` 3. **分布式一致性**: - 使用ZooKeeper管理机器ID分配 - 数据库方案需用`SELECT ... FOR UPDATE`实现行级锁 --- **最终建议**: - 金融交易类系统:优先选择分段数据库方案 - 物联网高频场景:使用压缩版Snowflake - 简单临时需求:时间戳+随机数组合
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值