生活随笔
收集整理的這篇文章主要介紹了
分布式全局ID生成器设计
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
項目是分布式的架構(gòu),需要設(shè)計一款分布式全局ID,參照了多種方案,博主最后基于snowflake的算法設(shè)計了一款自用ID生成器。具有以下優(yōu)勢:
保證分布式場景下生成的ID是全局唯一的生成的全局ID整體上是呈自增趨勢的,也就是說整體是粗略有序的高性能,能快速產(chǎn)生ID,本機(jī)(I7-6400HQ)單線程可以達(dá)到每秒生成近40萬個ID只占64bit位空間,可以根據(jù)業(yè)務(wù)需求擴(kuò)展在前綴或后綴拼接業(yè)務(wù)標(biāo)志位轉(zhuǎn)化為字符串。 UUID方案
- UUID:UUID長度128bit,32個16進(jìn)制字符,占用存儲空間多,且生成的ID是無序的
- 對于InnoDB這種聚集主鍵類型的引擎來說,數(shù)據(jù)會按照主鍵進(jìn)行排序,由于UUID的無序性,InnoDB會產(chǎn)生巨大的IO壓力,此時不適合使用UUID做物理主鍵,可以把它作為邏輯主鍵,物理主鍵依然使用自增ID。
- 組成部分:當(dāng)前日期和時間、時鐘序列、機(jī)器識別碼
數(shù)據(jù)庫生成全局ID方案
- 結(jié)合數(shù)據(jù)庫維護(hù)一個Sequence表,每當(dāng)需要為某個表的新紀(jì)錄生成ID時就從Sequence表中取出對應(yīng)的nextid,將其+1后更新到數(shù)據(jù)庫中以備下次使用。
- 由于所有的插入都要訪問該表,很容易造成性能瓶頸,且存在單點問題,如果該表所在的數(shù)據(jù)庫失效,全部應(yīng)用無法工作。
- 在高并發(fā)場景下,無法保證高性能。
snowflake方案
是一個優(yōu)秀的分布式Id生成方案,是Scala實現(xiàn)的,此次項目就是基于snowflake算法基礎(chǔ)上設(shè)計的Java優(yōu)化版
- 1位,不用。二進(jìn)制中最高位為1的都是負(fù)數(shù),但是我們生成的id一般都使用整數(shù),所以這個最高位固定是0
- 41位,用來記錄時間戳(毫秒),41位可以表示2^41^?1個數(shù)字,也就是說41位可以表示2^41^?1個毫秒的值,轉(zhuǎn)化成單位年則是(2^41?1)/(1000?60?60?24?365)=69年
- 10位,用來記錄工作機(jī)器id。可以部署在2^10^=1024個節(jié)點,包括5位datacenterId和5位workerId,5位(bit)可以表示的最最大正整數(shù)是2^5?1=31,即可以用0、1、2、3、....31這32個數(shù)字,來表示不同的datecenterId或workerId
- 12位,序列號,用來記錄同毫秒內(nèi)產(chǎn)生的不同id。12位(bit)可以表示的最大正整數(shù)是2^12^?1=4095,即可以用0、1、2、3、....4095這4096個數(shù)字,來表示同一機(jī)器同一時間截(毫秒)內(nèi)產(chǎn)生的4096個ID序號
改進(jìn)方案
全局唯一ID生成結(jié)構(gòu)如下(每部分用-分開):
- 0 - 00 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 00 - 00000000
- 1位標(biāo)識,由于long基本類型在Java中是帶符號的,最高位是符號位,正數(shù)是0,負(fù)數(shù)是1,所以id一般是正數(shù),最高位是0
- 2位生成發(fā)布方式,0代表嵌入式發(fā)布、1代表中心服務(wù)器發(fā)布模式、2代表rest發(fā)布方式、3代表測試方式
- 41位時間截(毫秒級),注意,41位時間截不是存儲當(dāng)前時間的時間截,而是存儲時間截的差值(當(dāng)前時間截 - 開始時間截得到的值),這里的的開始時間截,一般是我們的id生成器開始使用的時間,由我們程序來指定的。41位的時間截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
- 12位序列,毫秒內(nèi)的計數(shù),12位的計數(shù)順序號支持每個節(jié)點每毫秒(同一機(jī)器,同一時間截)產(chǎn)生4096個ID序號
- 8位的數(shù)據(jù)機(jī)器位,可以部署在256個節(jié)點,包括8位workerId
- 加起來剛好64位,為一個Long型
- 優(yōu)點是,整體上按照時間自增排序,并且整個分布式系統(tǒng)內(nèi)不會產(chǎn)生ID碰撞(機(jī)器ID作區(qū)分),并且效率較高,經(jīng)本地測試每秒能夠產(chǎn)生40萬ID左右。
方案優(yōu)勢
保證分布式場景下生成的ID是全局唯一的生成的全局ID整體上是呈自增趨勢的,也就是說整體是粗略有序的高性能,能快速產(chǎn)生ID,本機(jī)單線程可以達(dá)到每秒生成近40萬個ID只占64bit位空間,可以根據(jù)業(yè)務(wù)需求在前綴或后綴拼接業(yè)務(wù)標(biāo)志位。import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public final class IdGenerate {// ==============================Fields===========================================/** 開始時間截 (2018-01-01) */private final long twepoch = 1514736000000L;/** 機(jī)器id所占的位數(shù) */private final long workerIdBits = 8L;/** 序列在id中占的位數(shù) */private final long sequenceBits = 12L;/** 毫秒級別時間截占的位數(shù) */private final long timestampBits = 41L;/** 生成發(fā)布方式所占的位數(shù) */private final long getMethodBits = 2L;/** 支持的最大機(jī)器id,結(jié)果是255 (這個移位算法可以很快的計算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/** 生成序列向左移8位(8) */private final long sequenceShift = workerIdBits;/** 時間截向左移20位(12+8) */private final long timestampShift = sequenceBits + workerIdBits;/** 生成發(fā)布方式向左移61位(41+12+8) */private final long getMethodShift = timestampBits + sequenceBits + workerIdBits;/** 工作機(jī)器ID(0~255) */private long workerId = 0L;/** 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */private final long sequenceMask = -1L ^ (-1L << sequenceBits);/** 毫秒內(nèi)序列(0~4095) */private long sequence = 0L;/** 上次生成ID的時間截 */private long lastTimestamp = -1L;/** 2位生成發(fā)布方式,0代表嵌入式發(fā)布、1代表中心服務(wù)器發(fā)布模式、2代表rest發(fā)布方式、3代表保留未用 */private long getMethod = 0L;/** 成發(fā)布方式的掩碼,這里為3 (0b11=0x3=3) */private long maxGetMethod = -1L ^ (-1L << getMethodBits);/** 重入鎖*/private Lock lock = new ReentrantLock();//==============================Constructors=====================================/*** 構(gòu)造函數(shù)* @param 發(fā)布方式 0代表嵌入式發(fā)布、1代表中心服務(wù)器發(fā)布模式、2代表rest發(fā)布方式、3代表保留未用 (0~3)* @param workerId 工作ID (0~255)*/public IdGenerate(long getMethod, long workerId) {if (getMethod > maxGetMethod || getMethod < 0) {throw new IllegalArgumentException(String.format("getMethod can't be greater than %d or less than 0", maxGetMethod));}if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}this.getMethod = getMethod;this.workerId = workerId;}public long[] nextId(int nums) {long[] ids = new long[nums];for (int i = 0; i < nums; i++) {ids[i] = nextId();}return ids;}// ==============================Methods==========================================/*** 獲得下一個ID (該方法是線程安全的)* @return SnowflakeId*/public long nextId() {long timestamp = timeGen();//如果當(dāng)前時間小于上一次ID生成的時間戳,說明系統(tǒng)時鐘回退過這個時候應(yīng)當(dāng)拋出異常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一時間生成的,則進(jìn)行毫秒內(nèi)序列if (lastTimestamp == timestamp) {lock.lock();try {sequence = (sequence + 1) & sequenceMask;//毫秒內(nèi)序列溢出if (sequence == 0) {//阻塞到下一個毫秒,獲得新的時間戳timestamp = tilNextMillis(lastTimestamp);}}finally {lock.unlock();}}//時間戳改變,毫秒內(nèi)序列重置else {sequence = 0L;}//上次生成ID的時間截lastTimestamp = timestamp;//移位并通過或運(yùn)算拼到一起組成64位的IDreturn (getMethod << getMethodShift) // 生成方式占用2位,左移61位| ((timestamp - twepoch) << timestampShift) // 時間差占用41位,最多69年,左移20位| (sequence << sequenceShift) // 毫秒內(nèi)序列,取值范圍0-4095| workerId; // 工作機(jī)器,取值范圍0-255}public String nextString() {return Long.toString(nextId());}public String[] nextString(int nums) {String[] ids = new String[nums];for (int i = 0; i < nums; i++) {ids[i] = nextString();}return ids;}public String nextCode(String prefix) {StringBuilder sb = new StringBuilder(prefix);long id = nextId();sb.append(id);return sb.toString();}/*** 此方法可以在前綴上增加業(yè)務(wù)標(biāo)志* @param prefix* @param nums* @return*/public String[] nextCode(String prefix, int nums) {String[] ids = new String[nums];for (int i = 0; i < nums; i++) {ids[i] = nextCode(prefix);}return ids;}public String nextHexString() {return Long.toHexString(nextId());}/*** 阻塞到下一個毫秒,直到獲得新的時間戳* @param lastTimestamp 上次生成ID的時間截* @return 當(dāng)前時間戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒為單位的當(dāng)前時間* @return 當(dāng)前時間(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/*** 測試***/public static void main(String[] args) {IdGenerate idGenerate = new IdGenerate(0, 0);int count = 100000;//線程數(shù)=count*countfinal long[][] times = new long[count][100];Thread[] threads = new Thread[count];for (int i = 0; i < threads.length; i++) {final int ip = i;threads[i] = new Thread() {@Overridepublic void run() {for (int j = 0; j <100; j++) {long t1 = System.nanoTime();//該函數(shù)是返回納秒的。1毫秒=1納秒*1000000idGenerate.nextId();//測試long t = System.nanoTime() - t1;times[ip][j] = t;//求平均}}};}long lastMilis = System.currentTimeMillis();//逐個啟動線程for (int i = 0; i < threads.length; i++) {threads[i].start();}for (int i = 0; i < threads.length; i++) {try {threads[i].join();} catch (InterruptedException e) {e.printStackTrace();}}/*** 1、QPS:系統(tǒng)每秒處理的請求數(shù)(query per second)2、RT:系統(tǒng)的響應(yīng)時間,一個請求的響應(yīng)時間,也可以是一段時間的平均值3、最佳線程數(shù)量:剛好消耗完服務(wù)器瓶頸資源的臨界線程數(shù)對于單線程:QPS=1000/RT對于多線程:QPS=1000*線程數(shù)量/RT*/long time = System.currentTimeMillis() - lastMilis;System.out.println("QPS: "+ (1000*count /time));long sum = 0;long max = 0;for (int i = 0; i < times.length; i++) {for (int j = 0; j < times[i].length; j++) {sum += times[i][j];if (times[i][j] > max)max = times[i][j];}}System.out.println("Sum(ms)"+time);System.out.println("AVG(ms): " + sum / 1000000 / (count*100));System.out.println("MAX(ms): " + max / 1000000);}
}
測試結(jié)果
環(huán)境:CPU 雙核I7—6400HQ 系統(tǒng)win10
單線程下每秒產(chǎn)生近40萬個全局ID
模擬單個服務(wù)器并發(fā)場景:
1000線程并發(fā)下每個線程產(chǎn)生100個ID,共生產(chǎn)10萬個ID
- QPS: 2610
- Sum(ms)383
- AVG(ms): 0
- MAX(ms): 9
10000線程并發(fā)下每個線程產(chǎn)生100個ID,共生產(chǎn)100萬個ID
- QPS: 2701
- Sum(ms)3701
- AVG(ms): 0
- MAX(ms): 9
50000線程并發(fā)下每個線程產(chǎn)生100個ID,共生產(chǎn)500萬個ID
- QPS: 2720
- Sum(ms)18382
- AVG(ms): 0
- MAX(ms): 11
Ps:個人水平有限,如有錯誤,還請批評指正。
轉(zhuǎn)載于:https://www.cnblogs.com/keeya/p/9347617.html
總結(jié)
以上是生活随笔為你收集整理的分布式全局ID生成器设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。