雪花算法能知足高併發分佈式系統環境下ID不重複,而且基於時間戳生成的id具備時序性和惟一性,結構以下:node
由圖咱們能夠看出來,snowFlake ID結構是一個64bit的int型數據。算法
第1位bit:在二進制中最高位爲1,表示的是負數,由於咱們使用的id應該都是整數,因此這裏最高位應該是0。安全
41bit時間戳:41位能夠表示2^41-1個數字,若是隻用來表示正整數,能夠表示的數值範圍是:0 - (2^41 -1),這裏減去1的緣由就是由於數值範圍是從0開始計算的,而不是從1開始的。這裏的單位是毫秒,因此41位就能夠表示2^41-1個毫秒值,這樣轉化成單位年則是(2^41-1)/(1000 * 60 * 60 * 24 * 365) = 69併發
10bit-工做機器id:這裏是用來記錄工做機器的id。2^10=1024表示當前規則容許分佈式最大節點數爲1024個節點。這裏包括5位的workerID和5位的dataCenterID,這裏其實能夠不區分,但我下面的代碼進行了區分。less
12bit-序列號:用來記錄同毫秒內產生的不一樣id。12bit能夠表示的最大正整數是2^12-1=4095,便可以用0,1,2,3,......4094這4095個數字,表示同一機器同一時間戳(毫秒)內產生的4095個ID序號。分佈式
原理就是上面這些,沒有什麼難度吧,下面咱們看代碼如何實現:高併發
go的實現以下:ui
package main import ( "errors" "fmt" "sync" "time" ) // 由於snowFlake目的是解決分佈式下生成惟一id 因此ID中是包含集羣和節點編號在內的 const ( workerBits uint8 = 10 // 每臺機器(節點)的ID位數 10位最大能夠有2^10=1024個節點 numberBits uint8 = 12 // 表示每一個集羣下的每一個節點,1毫秒內可生成的id序號的二進制位數 即每毫秒可生成 2^12-1=4096個惟一ID // 這裏求最大值使用了位運算,-1 的二進制表示爲 1 的補碼,感興趣的同窗能夠本身算算試試 -1 ^ (-1 << nodeBits) 這裏是否是等於 1023 workerMax int64 = -1 ^ (-1 << workerBits) // 節點ID的最大值,用於防止溢出 numberMax int64 = -1 ^ (-1 << numberBits) // 同上,用來表示生成id序號的最大值 timeShift uint8 = workerBits + numberBits // 時間戳向左的偏移量 workerShift uint8 = numberBits // 節點ID向左的偏移量 // 41位字節做爲時間戳數值的話 大約68年就會用完 // 假如你2010年1月1日開始開發系統 若是不減去2010年1月1日的時間戳 那麼白白浪費40年的時間戳啊! // 這個一旦定義且開始生成ID後千萬不要改了 否則可能會生成相同的ID epoch int64 = 1525705533000 // 這個是我在寫epoch這個變量時的時間戳(毫秒) ) // 定義一個woker工做節點所須要的基本參數 type Worker struct { mu sync.Mutex // 添加互斥鎖 確保併發安全 timestamp int64 // 記錄時間戳 workerId int64 // 該節點的ID number int64 // 當前毫秒已經生成的id序列號(從0開始累加) 1毫秒內最多生成4096個ID } // 實例化一個工做節點 func NewWorker(workerId int64) (*Worker, error) { // 要先檢測workerId是否在上面定義的範圍內 if workerId < 0 || workerId > workerMax { return nil, errors.New("Worker ID excess of quantity") } // 生成一個新節點 return &Worker{ timestamp: 0, workerId: workerId, number: 0, }, nil } // 接下來咱們開始生成id // 生成方法必定要掛載在某個woker下,這樣邏輯會比較清晰 指定某個節點生成id func (w *Worker) GetId() int64 { // 獲取id最關鍵的一點 加鎖 加鎖 加鎖 w.mu.Lock() defer w.mu.Unlock() // 生成完成後記得 解鎖 解鎖 解鎖 // 獲取生成時的時間戳 now := time.Now().UnixNano() / 1e6 // 納秒轉毫秒 if w.timestamp == now { w.number++ // 這裏要判斷,當前工做節點是否在1毫秒內已經生成numberMax個ID if w.number > numberMax { // 若是當前工做節點在1毫秒內生成的ID已經超過上限 須要等待1毫秒再繼續生成 for now <= w.timestamp { now = time.Now().UnixNano() / 1e6 } } } else { // 若是當前時間與工做節點上一次生成ID的時間不一致 則須要重置工做節點生成ID的序號 w.number = 0 w.timestamp = now // 將機器上一次生成ID的時間更新爲當前時間 } // 第一段 now - epoch 爲該算法目前已經奔跑了xxx毫秒 // 若是在程序跑了一段時間修改了epoch這個值 可能會致使生成相同的ID //int64((now - epoch) << timeShift |w.datacenterId << 17 | (w.workerId << 12) | w.number) ID := int64((now-epoch)<<timeShift | (w.workerId << workerShift) | (w.number)) return ID } func main() { worker, err := NewWorker(1) if err != nil { fmt.Println(err) return } for i := 0; i < 10000; i++ { id := worker.GetId() fmt.Println(id) } }
C# 實現:this
public class IdWorker { //機器ID private static long workerId; private static long twepoch = 687888001020L; //惟一時間,這是一個避免重複的隨機量,自行設定不要大於當前時間戳 private static long sequence = 0L; private static int workerIdBits = 4; //機器碼字節數。4個字節用來保存機器碼(定義爲Long類型會出現,最大偏移64位,因此左移64位沒有意義) public static long maxWorkerId = -1L ^ -1L << workerIdBits; //最大機器ID private static int sequenceBits = 10; //計數器字節數,10個字節用來保存計數碼 private static int workerIdShift = sequenceBits; //機器碼數據左移位數,就是後面計數器佔用的位數 private static int timestampLeftShift = sequenceBits + workerIdBits; //時間戳左移動位數就是機器碼和計數器總字節數 public static long sequenceMask = -1L ^ -1L << sequenceBits; //一微秒內能夠產生計數,若是達到該值則等到下一微妙在進行生成 private long lastTimestamp = -1L; /// <summary> /// 機器碼 /// </summary> /// <param name="workerId"></param> public IdWorker(long workerId) { if (workerId > maxWorkerId || workerId < 0){ throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0 ", workerId)); } IdWorker.workerId = workerId; } public long nextId() { lock (this) { long timestamp = timeGen(); if (this.lastTimestamp == timestamp) { //同一微妙中生成ID IdWorker.sequence = (IdWorker.sequence + 1) & IdWorker.sequenceMask; //用&運算計算該微秒內產生的計數是否已經到達上限 if (IdWorker.sequence == 0) { //一微妙內產生的ID計數已達上限,等待下一微妙 timestamp = tillNextMillis(this.lastTimestamp); } } else { //不一樣微秒生成ID IdWorker.sequence = 0; //計數清0 } if (timestamp < lastTimestamp) { //若是當前時間戳比上一次生成ID時時間戳還小,拋出異常,由於不能保證如今生成的ID以前沒有生成過 throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", this.lastTimestamp - timestamp)); } this.lastTimestamp = timestamp; //把當前時間戳保存爲最後生成ID的時間戳 long nextId = (timestamp - twepoch << timestampLeftShift) | IdWorker.workerId << IdWorker.workerIdShift | IdWorker.sequence; return nextId; } } /// <summary> /// 獲取下一微秒時間戳 /// </summary> /// <param name="lastTimestamp"></param> /// <returns></returns> private long tillNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /// <summary> /// 生成當前時間戳 /// </summary> /// <returns></returns> private long timeGen() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; } } class Program { static void Main(string[] args) { IdWorker idworker = new IdWorker(1); for (int i = 0; i < 1000; i++) { Console.WriteLine(idworker.nextId()); } } }