全域性唯一ID生成器(SnowFlakeId演算法JAVA實現)
阿新 • • 發佈:2019-01-03
import org.apache.commons.lang3.RandomUtils; import java.util.Random; /** * @Description: 全域性唯一Id生成器 * @Author: guanfengliang * @Email: [email protected] * @Create: 2018-11-20 16:55 */ public class SnowFlakeWorker { /** 開始時間截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 機器id所佔的位數 */ private final long workerIdBits = 5L; /** 資料標識id所佔的位數 */ private final long datacenterIdBits = 5L; /** 支援的最大機器id,結果是31 (這個移位演算法可以很快的計算出幾位二進位制數所能表示的最大十進位制數) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 支援的最大資料標識id,結果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** 序列在id中佔的位數 */ private final long sequenceBits = 12L; /** 機器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 資料標識id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** 時間截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工作機器ID(0~31) */ private long workerId; /** 資料中心ID(0~31) */ private long datacenterId; /** 毫秒內序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的時間截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 建構函式 * @param workerId 工作ID (0~31) * @param datacenterId 資料中心ID (0~31) */ public SnowFlakeWorker(long workerId, long 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.workerId = workerId; this.datacenterId = datacenterId; } // ==============================Methods========================================== /** * 獲得下一個ID (該方法是執行緒安全的) * @return SnowflakeId */ public synchronized 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) { sequence = (sequence + 1) & sequenceMask; //毫秒內序列溢位 if (sequence == 0) { //阻塞到下一個毫秒,獲得新的時間戳 timestamp = tilNextMillis(lastTimestamp); } } //時間戳改變,毫秒內序列重置 else { sequence = generateRandom(); } //上次生成ID的時間截 lastTimestamp = timestamp; //移位並通過或運算拼到一起組成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } //生成的ID,例如message-id/ order-id/ tiezi-id,在資料量大時往往需要分庫分表,這些ID經常作為取模分庫分表的依據,為了分庫分表後資料均勻,ID生成往往有“取模隨機性”的需求,所以我們通常把每秒內的序列號放在ID的最末位,保證生成的ID是隨機的。 //又如果,我們在跨毫秒時,序列號總是歸0,會使得序列號為0的ID比較多,導致生成的ID取模後不均勻。解決方法是,序列號不是每次都歸0,而是歸一個0到9的隨機數,這個地方。 protected long generateRandom() { return RandomUtils.nextLong(0,10); } /** * 阻塞到下一個毫秒,直到獲得新的時間戳 * @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) { SnowFlakeWorker idWorker = new SnowFlakeWorker(0, 0); for (int i = 0; i < 100; i++) { long id = idWorker.nextId(); //System.out.println(Long.toBinaryString(id)); System.out.println(id); } } }