参考雪花算法,将当前时间减去基准时间(8位36进制) + 自增序列(4位36进制) + 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制)。
例如:当前时间和20100101的差值(8位36进制可使用89年,9位可以使用3220年) + 自增序列(4位36进制,每毫秒支持160万个自增序列值) +服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成至少16亿个不重复ID(实际笔记本测试可达400万个ID/秒)
使用36进制的原因:
主要是解决雪花算法时间回拨导致ID重复问题,增加ID的实际精度实现唯一性。之所以不采用62进制是因为ID有可能用于文件名,部分数据库可能设置为不区分大小写的场景,而且62进制对ID的整体长度影响不大。
import java.security.SecureRandom;
/**
* 20位字符串Id生成类
*
* @author huangsq
* @version 1.0, 2018-02-19
*/
public class StringIdGenerator implements IdGenerator<String> {
/**
* 开始时间截 (20100101)
*/
public static final long START_TIME = 1262275200000L;
public static final int MIN_SEQUENCE = 50000;
public static final int MAX_SEQUENCE = 1679615;
/**
* 支持的最大服务器ID标识
*/
public static final long MAX_SERVER_ID = 9999;
/**
* 默认进制
*/
private static final int RADIX = 36;
/**
* 机器id所占的位数
*/
private static final long workerIdBits = 12L;
/**
* 数据标识id所占的位数
*/
private static final long datacenterIdBits = 12L;
/**
* 支持的最大机器id,结果是4095
*/
private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支持的最大数据标识id,结果是4095
*/
private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 机器ID向左移12位
*/
private final long workerIdShift = 0;
/**
* 数据标识id向左移17位(12+5)
*/
private final long datacenterIdShift = workerIdBits;
private SecureRandom random = new SecureRandom();
private int sequence = MIN_SEQUENCE;
public static void main(String[] args) {
System.out.println(maxWorkerId);
}
/**
* 上次生成ID的时间截
*/
private long lastTimestamp = -1L;
/**
* 服务器ID(0~9999)
*/
private int serverId;
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public StringIdGenerator(int workerId, int 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.serverId = (datacenterId << datacenterIdShift) | (workerId << workerIdShift);
}
/**
* 生成20位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成16亿个不重复ID(实际400万个ID/秒)。
*
* @return
* @author huangsq
*/
public String nextId() {
return nextId(serverId);
}
/**
* 生成20位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 服务器ID(4位10进制:worker+datacenter) + 随机数(4位10进制,可拆分8192个数据库),理论上不同服务器每秒支持生成16亿个不重复ID(实际400万个ID/秒)。
*
* @param serverId 服务器ID(worker+datacenter)
* @return
* @author huangsq
*/
public String nextId(int serverId) {
if (serverId > MAX_SERVER_ID || serverId < 0) {
throw new IllegalArgumentException(String.format("serverId can't be > %d or < 0", MAX_SERVER_ID));
}
StringBuilder idString = timeIdBuilder(RADIX);
appendId(idString, serverId);
appendId(idString, random.nextInt(10000));
return idString.toString();
}
/**
* 生成16位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
* 随机数(4位10进制),理论上每秒支持生成16亿个不重复ID。多用于临时文件名。
*
* @return
* @author huangsq
*/
public String timeId() {
StringBuilder idString = timeIdBuilder(RADIX);
appendId(idString, random.nextInt(10000));
return idString.toString();
}
/**
* 生成12位字符串ID:当前时间和20100101的差值(8位36进制可使用89年,9位3220年) + 自增序列(4位36进制,160万个自增序列值) +
*
* @return
* @author huangsq
*/
private synchronized StringBuilder timeIdBuilder(int radix) {
long timestamp = timeGen();
// 如果是同一时间生成的,则进行毫秒内序列
++sequence;
if (sequence > MAX_SEQUENCE) {
sequence = MIN_SEQUENCE;
if (lastTimestamp == timestamp) { // 毫秒内序列溢出
// 阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
lastTimestamp = timestamp;
StringBuilder idString = new StringBuilder();
idString.append(NumberUtil.toString(timestamp - START_TIME, radix));
idString.append(NumberUtil.toString(sequence, radix));
return idString;
}
private void appendId(StringBuilder idString, int number) {
if (number < 1) {
idString.append("0000");
} else if (number < 10) {
idString.append("000").append(number);
} else if (number < 100) {
idString.append("00").append(number);
} else if (number < 1000) {
idString.append('0').append(number);
} else {
idString.append(number);
}
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
private static long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
private static long timeGen() {
return System.currentTimeMillis();
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* 进制转换工具类
* @author huangsq
* @version 1.0, 2018-02-19
*/
public final class NumberUtil {
static final String CHAR62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static final char[] digits = CHAR62.toCharArray();
static final Map<Character, Integer> digitMap = new HashMap<Character, Integer>();
/**
* 支持的最大进制数
*/
public static final int MAX_RADIX = digits.length;
/**
* 支持的最小进制数
*/
public static final int MIN_RADIX = 2;
/**
* 32进制
*/
public static final int RADIX32 = 32;
/**
* 36进制
*/
public static final int RADIX36 = 36;
/**
* 默认除法运算精度
*/
public static final int DEF_DIV_SCALE = 17;
/**
* 随机数
*/
private static final Random random = new Random();
static {
for (int i = 0; i < digits.length; i++) {
digitMap.put(digits[i], (int) i);
}
}
private NumberUtil() {
}
/**
* 将长整型数值转换为62进制数
* @param num
* @return
*/
public static String toString(long num) {
return toString(num, MAX_RADIX);
}
/**
* 将长整型数值转换为32进制数
* @param num
* @return
*/
public static String toString32(long num) {
return toString(num, RADIX32);
}
/**
* 将长整型数值转换为36进制数
* @param num
* @return
*/
public static String toString36(long num) {
return toString(num, RADIX36);
}
/**
* 将长整型数值转换为指定的进制数(最大支持62进制,字母数字已经用尽)
* @param num
* @param radix
* @return
*/
public static String toString(long num, int radix) {
if (radix < MIN_RADIX || radix > MAX_RADIX)
radix = 10;
if (radix == 10)
return Long.toString(num);
final int size = 65;
int charPos = 64;
char[] buf = new char[size];
boolean negative = (num < 0);
if (!negative) {
num = -num;
}
while (num <= -radix) {
buf[charPos--] = digits[(int) (-(num % radix))];
num = num / radix;
}
buf[charPos] = digits[(int) (-num)];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (size - charPos));
}
/**
* 将62进制字符串转换为长整型数字
* @param str 数字字符串
* @param radix 进制数
* @return
*/
public static long toNumber(String str) {
return toNumber(str, MAX_RADIX);
}
/**
* 将32进制字符串转换为长整型数字
* @param str 数字字符串
* @param radix 进制数
* @return
*/
public static long toNumber32(String str) {
return toNumber(str, RADIX32);
}
/**
* 将36进制字符串转换为长整型数字
* @param str 数字字符串
* @param radix 进制数
* @return
*/
public static long toNumber36(String str) {
return toNumber(str, RADIX36);
}
/**
* 将字符串转换为长整型数字
* @param str 数字字符串
* @param radix 进制数
* @return
*/
public static long toNumber(String str, int radix) {
if (str == null) {
throw new NumberFormatException("null");
}
if (radix < MIN_RADIX) {
throw new NumberFormatException("radix " + radix + " less than " + MIN_RADIX);
}
if (radix > MAX_RADIX) {
throw new NumberFormatException("radix " + radix + " greater than " + MAX_RADIX);
}
long result = 0;
boolean negative = false;
int len = str.length();
int i = 0;
long limit = -Long.MAX_VALUE;
long multmin;
Integer digit;
if (len > 0) {
char firstChar = str.charAt(0);
if (firstChar < '0') {
if (firstChar == '-') {
negative = true;
limit = Long.MIN_VALUE;
} else if (firstChar != '+') {
throw forInputString(str);
}
if (len == 1) {
throw forInputString(str);
}
i++;
}
multmin = limit / radix;
while (i < len) {
digit = digitMap.get(str.charAt(i++));
if (digit == null) {
throw forInputString(str);
}
if (digit < 0) {
throw forInputString(str);
}
if (result < multmin) {
throw forInputString(str);
}
result *= radix;
if (result < limit + digit) {
throw forInputString(str);
}
result -= digit;
}
} else {
throw forInputString(str);
}
return negative ? result : -result;
}
/**
* 随机生成指定长度的字符串,由数字、小写字母和大写字母组成
* @param length 字符串长度
* @return
*/
public static String random(int length) {
return random(length, RADIX36);
}
/**
* 随机生成指定长度的字符串,由数字、小写字母和大写字母组成
* @param length 字符串长度
* @param radix 进制数
* @return
*/
public static String random(int length, int radix) {
char[] cs = new char[length];
for (int i = 0; i < length; i++) {
cs[i] = digits[random.nextInt(radix)];
}
return new String(cs);
}
private static NumberFormatException forInputString(String s) {
return new NumberFormatException("For input string: \"" + s + "\"");
}
}
欢迎留言分享您认为更优秀的分布式ID算法!