項目是分佈式的架構,須要設計一款分佈式全局ID,參照了多種方案,博主最後基於snowflake的算法設計了一款自用ID生成器。具備如下優點:java
- 保證分佈式場景下生成的ID是全局惟一的
- 生成的全局ID總體上是呈自增趨勢的,也就是說總體是粗略有序的
- 高性能,能快速產生ID,本機(I7-6400HQ)單線程能夠達到每秒生成近40萬個ID
- 只佔64bit位空間,能夠根據業務需求擴展在前綴或後綴拼接業務標誌位轉化爲字符串。
是一個優秀的分佈式Id生成方案,是Scala實現的,這次項目就是基於snowflake算法基礎上設計的Java優化版算法
全局惟一ID生成結構以下(每部分用-分開):數據庫
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; /** 機器id所佔的位數 */ private final long workerIdBits = 8L; /** 序列在id中佔的位數 */ private final long sequenceBits = 12L; /** 毫秒級別時間截佔的位數 */ private final long timestampBits = 41L; /** 生成發佈方式所佔的位數 */ private final long getMethodBits = 2L; /** 支持的最大機器id,結果是255 (這個移位算法能夠很快的計算出幾位二進制數所能表示的最大十進制數) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 生成序列向左移8位(8) */ private final long sequenceShift = workerIdBits; /** 時間截向左移20位(12+8) */ private final long timestampShift = sequenceBits + workerIdBits; /** 生成發佈方式向左移61位(41+12+8) */ private final long getMethodShift = timestampBits + sequenceBits + workerIdBits; /** 工做機器ID(0~255) */ private long workerId = 0L; /** 生成序列的掩碼,這裏爲4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 毫秒內序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的時間截 */ private long lastTimestamp = -1L; /** 2位生成發佈方式,0表明嵌入式發佈、1表明中心服務器發佈模式、2表明rest發佈方式、3表明保留未用 */ private long getMethod = 0L; /** 成發佈方式的掩碼,這裏爲3 (0b11=0x3=3) */ private long maxGetMethod = -1L ^ (-1L << getMethodBits); /** 重入鎖*/ private Lock lock = new ReentrantLock(); //==============================Constructors===================================== /** * 構造函數 * @param 發佈方式 0表明嵌入式發佈、1表明中心服務器發佈模式、2表明rest發佈方式、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(); //若是當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當拋出異常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //若是是同一時間生成的,則進行毫秒內序列 if (lastTimestamp == timestamp) { lock.lock(); try { sequence = (sequence + 1) & sequenceMask; //毫秒內序列溢出 if (sequence == 0) { //阻塞到下一個毫秒,得到新的時間戳 timestamp = tilNextMillis(lastTimestamp); } }finally { lock.unlock(); } } //時間戳改變,毫秒內序列重置 else { sequence = 0L; } //上次生成ID的時間截 lastTimestamp = timestamp; //移位並經過或運算拼到一塊兒組成64位的ID return (getMethod << getMethodShift) // 生成方式佔用2位,左移61位 | ((timestamp - twepoch) << timestampShift) // 時間差佔用41位,最多69年,左移20位 | (sequence << sequenceShift) // 毫秒內序列,取值範圍0-4095 | workerId; // 工做機器,取值範圍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(); } /** * 此方法能夠在前綴上增長業務標誌 * @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 當前時間戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒爲單位的當前時間 * @return 當前時間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } //==============================Test============================================= /** * 測試 * * */ public static void main(String[] args) { IdGenerate idGenerate = new IdGenerate(0, 0); int count = 100000;//線程數=count*count final 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() { @Override public void run() { for (int j = 0; j <100; j++) { long t1 = System.nanoTime();//該函數是返回納秒的。1毫秒=1納秒*1000000 idGenerate.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(); } } /** * 一、QPS:系統每秒處理的請求數(query per second) 二、RT:系統的響應時間,一個請求的響應時間,也能夠是一段時間的平均值 三、最佳線程數量:恰好消耗完服務器瓶頸資源的臨界線程數 對於單線程:QPS=1000/RT 對於多線程:QPS=1000*線程數量/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); } }
環境:CPU 雙核I7—6400HQ 系統win10
單線程下每秒產生近40萬個全局ID安全
Ps:我的水平有限,若有錯誤,還請批評指正。服務器