分布式主键ID的生成方案有以下几种:
缺点:
缺点:
缺点:
雪花算法是 Twitter 开源的主键生成算法 snowflake
它用64位二进制表示主键,由5部分组成:
雪花算法的优点:
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.Random;
public class SnowflakeIdGenerator {
/**
* 时间戳标识所占二进制位数
*/
private static final int TIME_STAMP_BIT_LEN = 41;
/**
* 机房标识所占二进制位数
*/
private static final int SERVER_ROOM_BIT_LEN = 5;
/**
* 服务器标识所占二进制位数
*/
private static final int SERVER_BIT_LEN = 5;
/**
* 每毫秒中序列所占二进制位数
*/
private static final int SEQ_BIT_LEN = 12;
/**
* 时间戳标识向左移动的位数(这里的1标识最高位)
*/
private static final int TIME_STAMP_LEFT_BIT_LEN = 64 - 1 - TIME_STAMP_BIT_LEN;
/**
* 机房标识左移位数
*/
private static final int SERVER_ROOM_LEFT_BIT_LEN = TIME_STAMP_LEFT_BIT_LEN - SERVER_ROOM_BIT_LEN;
/**
* 服务器标识左移位数
*/
private static final int SERVER_LEFT_BIT_LEN = SERVER_ROOM_LEFT_BIT_LEN - SERVER_BIT_LEN;
/**
* 开始时间戳,此处为 2022年4月9日
*/
private static final long START_TIME_STAMP = 1649497879948L;
/**
* 上次生成ID的时间戳
*/
private static long LAST_TIME_STAMP = -1L;
/**
* 上一次毫秒内存序列值
*/
private static long LAST_SEQ = 0L;
/**
* 获取机房标识(可以手动定义0-31之间的数)
*/
private static final long SERVER_ROOM_ID = getServerRoomId();
/**
* 获取服务器标识(可以手动定义0-31之间的数)
*/
private static final long SERVER_ID = getServerId();
/**
* 机房标识最大值 +1
*/
private static final int SERVER_ROOM_MAX_NUM_1 = ~(-1 << SERVER_ROOM_BIT_LEN) + 1;
/**
* 服务器标识最大值 +1
*/
private static final int SERVER_MAX_NUM_1 = ~(-1 << SERVER_BIT_LEN) + 1;
/**
* 毫秒内存列的最大值
*/
private static final long SEQ_MAX_NUM = ~(-1 << SEQ_BIT_LEN);
/**
* 对服务器地址的哈希码取余作为服务器标识
* TODO 根据实际环境修改该方法,该方法不能应用于开发环境,此处仅作为例子
*
* @return 服务器标识
*/
private static int getServerId() {
try {
String hostAddress = Inet4Address.getLocalHost().getHostAddress();
return (hostAddress.hashCode() & Integer.MAX_VALUE ) % SERVER_MAX_NUM_1;
} catch (UnknownHostException e) {
return new Random().nextInt(SERVER_MAX_NUM_1);
}
}
/**
* 对服务器名称的哈希码取余作为机房标识
* TODO 根据实际环境修改该方法,该方法不能应用于开发环境,此处仅作为例子
*
* @return 机房标识
*/
private static int getServerRoomId() {
try {
String hostName = Inet4Address.getLocalHost().getHostName();
return (hostName.hashCode() & Integer.MAX_VALUE) % SERVER_ROOM_MAX_NUM_1;
} catch (Exception e) {
return new Random().nextInt(SERVER_ROOM_MAX_NUM_1);
}
}
/**
* 一直循环直到获取到下毫秒的时间戳
*
* @param lastMillis
* @return 下一毫秒的时间戳
*/
private static long nextMillis(long lastMillis) {
long now = System.currentTimeMillis();
while (now <= lastMillis) {
now = System.currentTimeMillis();
}
return now;
}
/**
* 生成唯一ID
* 须加锁避免并发问题
*
* @return 返回唯一ID
*/
public synchronized static long generateUniqueId() {
long currentTimeStamp = System.currentTimeMillis();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时间回退过,此时因抛出异常
if (currentTimeStamp < LAST_TIME_STAMP) {
throw new RuntimeException(String.format("系统时间错误! %d 毫秒内拒绝生成雪花ID", START_TIME_STAMP));
}
if (currentTimeStamp == LAST_TIME_STAMP) {
LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
if (LAST_SEQ == 0) {
currentTimeStamp = nextMillis(LAST_TIME_STAMP);
}
} else {
LAST_SEQ = 0;
}
// 上次生成ID的时间戳
LAST_TIME_STAMP = currentTimeStamp;
return ((currentTimeStamp - START_TIME_STAMP) << TIME_STAMP_LEFT_BIT_LEN | (SERVER_ROOM_ID << SERVER_ROOM_LEFT_BIT_LEN) | (SERVER_ID << SERVER_LEFT_BIT_LEN) | LAST_SEQ);
}
/**
* 主函数测试
*
* @param args
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
int num = 100;
for (int i = 0; i < num; i++) {
System.out.println(generateUniqueId());
}
long end = System.currentTimeMillis();
System.out.println("共生成 " + num + " 个ID,用时 " + (end - start) + " 毫秒");
}
}