一、雪花算法介绍
雪花算法是Twitter公司开源的一种用于生成全局唯一ID的算法,它可以保证在分布式的情况下生成唯一ID,解决了传统的递增ID在分布式场景下可能重复的问题。
雪花算法生成的ID是一个64位的长整型,由以下几部分构成:
- 时间戳
- 机器ID
- 序列号
其中,时间戳占用41位,可以表示的时间范围为2^41/(1000*60*60*24*365),大约为69年。机器ID占用10位,可以表示的最大机器数为2^10-1,即1023个。序列号占用12位,可以表示的最大序号数为2^12-1,即4095个。
二、Java实现雪花算法
1、时间戳
首先,我们需要获取当前时间的时间戳。Java中可以使用System.currentTimeMillis()方法获取当前时间戳,不过它只能精确到毫秒级别,为了让我们的ID更加唯一,我们可以使用System.nanoTime()方法获取更加精确的时间戳,精确到纳秒级别。
/**
* 获取当前时间戳,精确到纳秒级别
* @return 当前时间戳(毫秒+纳秒)
*/
private static long getCurrentTimestamp() {
long timestamp = System.nanoTime();
return (timestamp - START_TIMESTAMP) / 1000000;
}
其中,START_TIMESTAMP是一个固定时间戳,用于计算时间戳的相对值。由于时间戳只占用了41位,所以时间戳的范围是有限的,为了避免时间戳满足之后,冲突概率变高的问题,我们可以将START_TIMESTAMP设置为一个固定的值,比如2018年1月1日。
2、机器ID
雪花算法中,机器ID是用于指示不同机器的一个标识符。在分布式系统中,不同的机器需要有不同的机器ID。可以通过配置文件等方式来获取机器ID,也可以使用网卡MAC地址等实际唯一的标识符来获取。
/**
* 获取机器ID,可以根据实际情况进行获取
* @return 机器ID
*/
private static long getMachineId() {
// TODO 根据实际情况获取机器ID
return 1;
}
3、序列号
序列号用于保证同一时间内,同一台机器生成不同的ID。为了避免序列号重复,我们需要在同一毫秒内,使用不同的序列号。另外,由于序列号只占用了12位,所以最多可以有4095个序列号,为了避免序列号使用完之后,需要等待到下一毫秒,我们可以将序列号的初始值设置为一个随机数。
/**
* 获取序列号,同一毫秒内生成不同的序列号
* @return 序列号
*/
private static long getSequence() {
long sequence = sequenceGenerator.getAndIncrement();
return sequence % SEQUENCE_LIMIT;
}
其中,sequenceGenerator是一个AtomicLong类型的变量,用于产生序列号,这个变量在程序启动时,会被初始化为一个随机数,用于保证每次重启程序时,序列号不会从0开始计数。
三、Java实现雪花算法示例代码
下面是一个完整的Java实现雪花算法的示例代码:
/**
* 雪花算法生成全局唯一ID
*/
public class SnowflakeIdGenerator {
// 起始的时间戳:2018-01-01
private static final long START_TIMESTAMP = 1514736000000L;
// 机器ID所占的位数
private static final long MACHINE_ID_BITS = 10L;
// 序列号所占的位数
private static final long SEQUENCE_BITS = 12L;
// 支持的最大机器ID,结果是1023
private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
// 支持的最大序列号,结果是4095
private static final long SEQUENCE_LIMIT = ~(-1L << SEQUENCE_BITS);
// 机器ID向左移12位
private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
// 时间戳向左移22位
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
// 机器ID
private static long machineId;
// 序列号生成器
private static AtomicLong sequenceGenerator = new AtomicLong(new Random().nextInt(100));
/**
* 初始化机器ID,可以根据实际情况进行初始化
*/
public static void initMachineId() {
SnowflakeIdGenerator.machineId = getMachineId();
if(machineId > MAX_MACHINE_ID || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than " + MAX_MACHINE_ID + " or less than 0.");
}
}
/**
* 生成唯一ID
* @return 返回64位的唯一ID
*/
public static synchronized long generateId() {
long timestamp = getCurrentTimestamp();
if(timestamp < 0) {
throw new IllegalStateException("Clock moved backwards, refuse to generate id.");
}
long sequence = getSequence();
long id = ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT) |
(machineId << MACHINE_ID_SHIFT) |
sequence;
return id;
}
/**
* 获取当前时间戳,精确到纳秒级别
* @return 当前时间戳(毫秒+纳秒)
*/
private static long getCurrentTimestamp() {
long timestamp = System.nanoTime();
return (timestamp - START_TIMESTAMP) / 1000000;
}
/**
* 获取机器ID,可以根据实际情况进行获取
* @return 机器ID
*/
private static long getMachineId() {
// TODO 根据实际情况获取机器ID
return 1;
}
/**
* 获取序列号,同一毫秒内生成不同的序列号
* @return 序列号
*/
private static long getSequence() {
long sequence = sequenceGenerator.getAndIncrement();
return sequence % SEQUENCE_LIMIT;
}
}