雪花算法:分布式唯一ID生成利器
前言
以分布式ID為例,它的生成往往會(huì)在唯一性、遞增性、高可用性、高性能等方面都有所要求。并且在業(yè)務(wù)處理時(shí),還要防止爬蟲(chóng)根據(jù)ID的自增進(jìn)行數(shù)據(jù)爬取。而雪花算法,在這些方面表現(xiàn)得都不錯(cuò)。
常見(jiàn)分布式ID生成
市面上比較常見(jiàn)的分布式ID生成算法及類(lèi)庫(kù):
UUID:Java自帶API,生成一串唯一隨機(jī)36位字符串(32個(gè)字符串+4個(gè)“-”)??梢员WC唯一性,但可讀性差,無(wú)法有序遞增。
SnowFlake:雪花算法,Twitter開(kāi)源的由64位整數(shù)組成分布式ID,性能較高,并且在單機(jī)上遞增。GitHub上官方地址:https://github.com/twitter-archive/snowflake/tree/snowflake-2010 。
UidGenerator:百度開(kāi)源的分布式ID生成器,基于雪花算法。GitHub參考鏈接:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 。該項(xiàng)目的說(shuō)明文檔及測(cè)試案例都值得深入學(xué)習(xí)一下。
Leaf:美團(tuán)開(kāi)源的分布式ID生成器,能保證全局唯一,趨勢(shì)遞增,但需要依賴(lài)關(guān)系數(shù)據(jù)庫(kù)、Zookeeper等中間件。相關(guān)實(shí)現(xiàn)可參考該文:https://tech.meituan.com/2017/04/21/mt-leaf.html 。
雪花算法
雪花(snowflake),美麗、獨(dú)特又變幻莫測(cè)。在大自然中幾乎找不到兩片完全一樣的雪花。雪花的這些特性正好在雪花算法上有所展示。
SnowFlake算法是Twitter開(kāi)源的分布式ID生成算法。核心思想就是:使用一個(gè)64 bit的 long 型的數(shù)字作為全局唯一ID。算法中還引入了時(shí)間戳,基本上保證了自增特性。
最初的版本的雪花算法是基于scala寫(xiě)的,當(dāng)然,不同的編程語(yǔ)言都可以根據(jù)其算法邏輯進(jìn)行實(shí)現(xiàn)。
雪花算法原理
SnowFlake算法生成ID的結(jié)果是一個(gè)64bit大小的整數(shù),結(jié)構(gòu)如下圖:

算法解析:
- 第一個(gè)部分:1個(gè)bit,無(wú)意義,固定為0。二進(jìn)制中最高位是符號(hào)位,1表示負(fù)數(shù),0表示正數(shù)。ID都是正整數(shù),所以固定為0。
- 第二個(gè)部分:41個(gè)bit,表示時(shí)間戳,精確到毫秒,可以使用69年。時(shí)間戳帶有自增屬性。
- 第三個(gè)部分:10個(gè)bit,表示10位的機(jī)器標(biāo)識(shí),最多支持1024個(gè)節(jié)點(diǎn)。此部分也可拆分成5位datacenterId和5位workerId,datacenterId表示機(jī)房ID,workerId表示機(jī)器ID。
- 第四部分:12個(gè)bit,表示序列化,即一些列的自增ID,可以支持同一節(jié)點(diǎn)同一毫秒生成最多4095個(gè)ID序號(hào)。
由于在Java中64bit的整數(shù)是long類(lèi)型,所以在Java中SnowFlake算法生成的id就是long來(lái)存儲(chǔ)的。
雪花算法Java實(shí)現(xiàn)
雪花算法Java工具類(lèi)實(shí)現(xiàn):
public class SnowFlake {
/**
* 起始的時(shí)間戳(可設(shè)置當(dāng)前時(shí)間之前的鄰近時(shí)間)
*/
private final static long START_STAMP = 1480166465631L;
/**
* 序列號(hào)占用的位數(shù)
*/
private final static long SEQUENCE_BIT = 12;
/**
* 機(jī)器標(biāo)識(shí)占用的位數(shù)
*/
private final static long MACHINE_BIT = 5;
/**
* 數(shù)據(jù)中心占用的位數(shù)
*/
private final static long DATA_CENTER_BIT = 5;
/**
* 每一部分的最大值
*/
private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);
private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
/**
* 數(shù)據(jù)中心ID(0~31)
*/
private final long dataCenterId;
/**
* 工作機(jī)器ID(0~31)
*/
private final long machineId;
/**
* 毫秒內(nèi)序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的時(shí)間截
*/
private long lastStamp = -1L;
public SnowFlake(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("dataCenterId can't be greater than MAX_DATA_CENTER_NUM or less than " +
"0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
/**
* 產(chǎn)生下一個(gè)ID
*/
public synchronized long nextId() {
long currStamp = getNewStamp();
if (currStamp < lastStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStamp == lastStamp) {
//相同毫秒內(nèi),序列號(hào)自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列數(shù)已經(jīng)達(dá)到最大
if (sequence == 0L) {
//阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳
currStamp = getNextMill();
}
} else {
//不同毫秒內(nèi),序列號(hào)置為0
sequence = 0L;
}
lastStamp = currStamp;
// 移位并通過(guò)或運(yùn)算拼到一起組成64位的ID
return (currStamp - START_STAMP) << TIMESTAMP_LEFT //時(shí)間戳部分
| dataCenterId << DATA_CENTER_LEFT //數(shù)據(jù)中心部分
| machineId << MACHINE_LEFT //機(jī)器標(biāo)識(shí)部分
| sequence; //序列號(hào)部分
}
private long getNextMill() {
long mill = getNewStamp();
while (mill <= lastStamp) {
mill = getNewStamp();
}
return mill;
}
private long getNewStamp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(11, 11);
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
System.out.println(snowFlake.nextId());
}
System.out.println(System.currentTimeMillis() - start);
}
}
上述代碼中,在算法的核心方法上,通過(guò)加synchronized鎖來(lái)保證線程安全。這樣,同一服務(wù)器線程是安全的,生成的ID不會(huì)出現(xiàn)重復(fù),而不同服務(wù)器由于機(jī)器碼不同,就算同一時(shí)刻兩臺(tái)服務(wù)器都產(chǎn)生了雪花ID,結(jié)果也是不一樣的。
其他問(wèn)題
41位時(shí)間戳最長(zhǎng)只能有69年
下面來(lái)用程序推算一下,41位時(shí)間戳為什么只能支持69年。
41的二進(jìn)制,最大值也就41位都是1,也就是說(shuō)41位可以表示2^{41}-1個(gè)毫秒的值,轉(zhuǎn)化成單位年則是(2^{41}-1) / (1000 * 60 * 60 * 24 *365) = 69年。
通過(guò)代碼驗(yàn)證一下:
public static void main(String[] args) {
//41位二進(jìn)制最小值
String minTimeStampStr = "00000000000000000000000000000000000000000";
//41位二進(jìn)制最大值
String maxTimeStampStr = "11111111111111111111111111111111111111111";
//轉(zhuǎn)10進(jìn)制
long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
//一年總共多少毫秒
long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
//算出最大可以多少年
System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
}
所以,雪花算法生成的ID只能保證69年內(nèi)不會(huì)重復(fù),如果超過(guò)69年的話,那就考慮換個(gè)服務(wù)器(服務(wù)器ID)部署,并且要保證該服務(wù)器的ID和之前都沒(méi)有重復(fù)過(guò)。
前后端數(shù)值類(lèi)型
在使用雪花算法時(shí),由于生成的ID是64位,在傳遞給前端時(shí),需要考慮以字符串的類(lèi)型進(jìn)行傳遞,否則可能會(huì)導(dǎo)致前端類(lèi)型溢出,再回傳到服務(wù)器時(shí)已經(jīng)變成另外一個(gè)值。
這是因?yàn)镹umber類(lèi)型的ID在JS中最大只支持53位,直接將雪花算法的生成的ID傳遞給JS,會(huì)導(dǎo)致溢出。
小結(jié)
生成唯一性ID(其他數(shù)據(jù))是幾乎在每個(gè)系統(tǒng)中都會(huì)有的場(chǎng)景,對(duì)其生成算法不僅要保證全局唯一性、趨勢(shì)遞增性,還要保證信息安全(比如被爬取數(shù)據(jù)),同時(shí)還要保證算法的高可用性(QPS、可行5個(gè)9、平均延時(shí)、TP999等指標(biāo))。這就對(duì)ID生成的算法有一定的要求,而雪花算法算是一個(gè)不錯(cuò)的選擇。
但它也是有一定的缺點(diǎn)的,比如強(qiáng)依賴(lài)機(jī)器時(shí)鐘,如果機(jī)器上的時(shí)鐘回?fù)?,?huì)導(dǎo)致重復(fù)或服務(wù)不可用的問(wèn)題,這也是我們?cè)谑褂脮r(shí)需要注意的事項(xiàng)。
































