go和C# 雪花算法

雪花算法能知足高併發分佈式系統環境下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());
            }
 
        }
 
 
    }
相關文章
相關標籤/搜索