自定義分佈式ID算法
算法原理
參考雪花算法,將當前時間減去基準時間(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的整體長度影響不大。
優點
- 毫秒數在高位,自增序列在低位,整個 ID 都是趨勢遞增的。(同一毫秒內排序可能不正確,因爲計數器沒做清零)
- 不依賴數據庫等第三方系統,以服務的方式部署,穩定性更高,生成 ID 的性能也是非常高的。可以達到400萬個ID/秒。
- 可以根據自身業務特性分配 bit 位,非常靈活。
- 支持類似mycat的分庫分表機制,可以根據策略進行分庫的框架,最大支持拆分8192個數據庫。
- ID長度在89年內保持20位,大於89年爲21位,有隨機數和自增序列可以保證數據的安全。
- 理論不存在時鐘回撥問題,計數器每毫秒不清零,並且每毫秒有9999個隨機數。
- 可支持本地批量生成,緩存ID。
缺點
- ID是20位字符串,相對自增序列佔用更大的存儲空間。
- 不滿足具備業務規則的id生成方式,例如希望以年月日開頭的id。
- 分庫分表只支持隨機或者根據worker+datacenter方式拆分。不支持類似自增序列的方式進行拆分。
- VARCHAR似乎比NUMBER類型性能差一些(oracle自己測試感覺差的不多),對索引的支持可能不同的數據庫有一些差異,例如對id有order by 或者是大於,小余等需求。
- 其他未知問題。
源碼
StringIdGenerator
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萬個自增序列值) +<br/>
* 服務器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萬個自增序列值) +<br/>
* 服務器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萬個自增序列值) +<br/>
* 隨機數(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萬個自增序列值) +<br/>
*
* @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();
}
}
NumberUtil
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算法!