1. 程式人生 > >go分散式生成全域性唯一ID

go分散式生成全域性唯一ID

因為snowFlake目的是解決分散式下生成唯一id 所以ID中是包含叢集和節點編號在內的
const (
    numberBits uint8 = 12 // 表示每個叢集下的每個節點,1毫秒內可生成的id序號的二進位制位 對應上圖中的最後一段
    workerBits uint8 = 10 // 每臺機器(節點)的ID位數 10位最大可以有2^10=1024個節點數 即每毫秒可生成 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 // 記錄上一次生成id的時間戳
	workerId int64 // 該節點的ID
	number int64 // 當前毫秒已經生成的id序列號(從0開始累加) 1毫秒內最多生成4096個ID
}
例項化一個工作節點
workerId 為當前節點的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
}
生成方法一定要掛載在某個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
        // 下面這段程式碼看到很多前輩都寫在if外面,無論節點上次生成id的時間戳與當前時間是否相同 都重新賦值  這樣會增加一丟丟的額外開銷 所以我這裡是選擇放在else裡面
        w.timestamp = now // 將機器上一次生成ID的時間更新為當前時間
    }

    ID := int64((now - epoch) << timeShift | (w.workerId << workerShift) | (w.number))
    return ID
}

開始介入測試檢視結果

func main() {
    // 測試指令碼

    // 生成節點例項
    worker, err := NewWorker(2)

    if err != nil {
        fmt.Println(err)
        return
    }

    ch := make(chan int64)
    count := 2
    // 併發 count 個 goroutine 進行 snowflake ID 生成
    for i := 0; i < count; i++ {
        go func() {
            id := worker.GetId()
            ch <- id
        }()
    }

    defer close(ch)

    m := make(map[int64]int)
    for i := 0; i < count; i++  {
        id := <- ch
        // 如果 map 中存在為 id 的 key, 說明生成的 snowflake ID 有重複
        _, ok := m[id]
        if ok {
            fmt.Println("ID is not unique!\n")
        }
        // 將 id 作為 key 存入 map
        m[id] = i
        fmt.Println(id)
    }
    // 成功生成 snowflake ID
    fmt.Println("All", count, "snowflake ID Get successed!")
}

生成結果 節點1 和節點2 分別生成的ID