在分布式系统中,生成唯一主键是一个常见的需求。在使用 Sharding-JDBC 进行分库分表时,生成唯一主键需要考虑到分布式环境下的唯一性和性能。以下是一种实战方案:
实现分布式唯一主键的方案
1. 使用数据库自增主键
对于单库单表的场景,可以使用数据库自增主键来生成唯一主键。但是在分库分表的情况下,由于每个库表都有独立的自增序列,无法保证全局唯一性。
2. 使用 UUID
可以使用 UUID 来生成唯一主键,UUID 保证了全局唯一性,但会导致主键过长,不利于索引和存储空间的利用。
3. 使用 Snowflake 算法
Snowflake 是一种分布式唯一 ID 生成算法,可以生成全局唯一且有序的 ID。它的特点是:
- 时间有序性:生成的 ID 是递增的,保证了索引的顺序性和性能。
- 唯一性:在同一时空中不会生成重复的 ID。
- 高性能:生成 ID 的性能非常高。
Snowflake 算法使用一个 64 位的 long 型数字作为全局唯一 ID,结构如下:
0 | timestamp(ms) | data center id | machine id | sequence
其中,timestamp(ms) 表示当前时间戳,data center id 和 machine id 可以根据实际情况分配,sequence 表示同一时间戳下的序列号,保证了同一毫秒内生成的 ID 的唯一性。
实现步骤
- 引入 Snowflake 算法的实现库,如 Twitter 的 Snowflake 或者自己实现一个 Snowflake 算法。
- 在应用启动时初始化 Snowflake 算法的实例。
- 每次生成唯一主键时调用 Snowflake 算法实例的
nextId()
方法获取唯一 ID。
示例代码
public class SnowflakeIdGenerator {
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
private final long twepoch = 1288834974657L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId, long datacenterId) {
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));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < 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 = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
}
注意事项
- 在分布式环境中,确保每个节点的 workerId 和 datacenterId 不重复。
- Snowflake 算法要求时钟不可回退,确保服务器的时钟同步性。
以上是使用 Snowflake 算法实现分布式唯一主键的方案。根据具体业务需求和分布式环境的特点,选择合适的方案来生成唯一主键。