Twitter snowflake ID 算法之 golang 實現

我的博客原文 Twitter snowflake ID 算法之 golang 實現

是什么驾凶?

snowflake ID 算法是 twitter 使用的唯一 ID 生成算法灾螃,為了滿足 Twitter 每秒上萬條消息的請求地回,使每條消息有唯一浙宜、有一定順序的 ID ,且支持分布式生成酬荞。

主要解決了高并發(fā)時 ID 生成不重復的問題

結構

snowflake ID 的結構是一個 64 bit 的 int 型數據搓劫。

如圖所示 :


snowflake-64bit

1 bit:不使用,可以是 1 或 0

41 bit:記錄時間戳 (當前時間戳減去用戶設置的初始時間混巧,毫秒表示)枪向,可記錄最多 69 年的時間戳數據

10 bit:用來記錄分布式節(jié)點 ID,一般每臺機器一個唯一 ID咧党,也可以多進程每個進程一個唯一 ID秘蛔,最大可部署 1024 個節(jié)點

12 bit:序列號,用來記錄不同 ID 同一毫秒時的序列號傍衡,最多可生成 4096 個序列號

時間戳深员、節(jié)點 ID 和序列號的位數可以根據業(yè)務自由浮動調整

唯一 ID 原理

假設在一個節(jié)點 (機器) 上,節(jié)點 ID 唯一蛙埂,并發(fā)時有多個線程去生成 ID辨液。
滿足以上條件時,如果多個線程在同一毫秒內生成 ID箱残,那么序列號步進 (加一),這里要保證序列號的操作并發(fā)安全,使同一毫秒內生成的 ID 擁有不同序列號被辑。如果序列號達到上限燎悍,則等待這一毫秒結束,在新的毫秒繼續(xù)步進盼理。

這樣保證了:
所有生成的 ID 按時間趨勢遞增 (有序)
整個分布式系統(tǒng)內不會產生重復 ID (唯一)

用 go 實現的思路

why go ?

go 有封裝好的協(xié)程 goroutine谈山,可以很好的處理并發(fā),可以加鎖保證數據的同步安全宏怔,有很好的性能奏路。當然其它語言如 Java、Scala 也是完全可以的臊诊。

思路

1鸽粉、確定唯一的節(jié)點 ID
2、設置一個初始時間戳 (毫秒表示)
3抓艳、處理并發(fā)時序列號步進和并發(fā)安全問題
4触机、組裝各個 bits ,生成最終的 64 bit ID

編碼實現

首先我們要引入基礎的模塊

import (
    "fmt"        // 測試玷或、打印
    "time"      // 獲取時間
    "errors"    // 生成錯誤
    "sync"      // 使用互斥鎖
)

基礎常量定義

這里求最大值使用了位運算儡首,-1 的二進制表示為 1 的補碼,感興趣的同學可以自己算算試試 -1 ^ (-1 << nodeBits) 這里是不是等于 1023

const (
    nodeBits  uint8 = 10          // 節(jié)點 ID 的位數
    stepBits  uint8 = 12            // 序列號的位數
    nodeMax   int64 = -1 ^ (-1 << nodeBits)   // 節(jié)點 ID 的最大值偏友,用于檢測溢出
    stepMax   int64 = -1 ^ (-1 << stepBits)    // 序列號的最大值蔬胯,用于檢測溢出
    timeShift uint8 = nodeBits + stepBits    // 時間戳向左的偏移量
    nodeShift uint8 = stepBits  // 節(jié)點 ID 向左的偏移量
)

設置初始時間的時間戳 (毫秒表示),我這里使用 twitter 設置的一個時間位他,這個可以隨意設置 氛濒,比現在的時間靠前即可。

var Epoch int64 = 1288834974657 // timestamp 2006-03-21:20:50:14 GMT

ID 結構和 Node 結構的實現
這里我們申明一個 int64 的 ID 類型 (這樣可以為此類型定義方法棱诱,比直接使用 int64 變量更靈活)

type ID int64

Node 結構用來存儲一個節(jié)點 (機器) 上的基礎數據

type Node struct {
    mu sync.Mutex        // 添加互斥鎖泼橘,保證并發(fā)安全
    timestamp int64      // 時間戳部分
    node      int64      // 節(jié)點 ID 部分  
    step      int64      // 序列號 ID 部分          
}

獲取 Node 類型實例的函數,用于獲得當前節(jié)點的 Node 實例

func NewNode(node int64) (*Node, error) {
    // 如果超出節(jié)點的最大范圍迈勋,產生一個 error
    if node < 0 || node > nodeMax {
        return nil, errors.New("Node number must be between 0 and 1023")
    }
    // 生成并返回節(jié)點實例的指針
    return &Node{
        timestamp: 0,
        node:      node,
        step:      0,
    }, nil
}

最后一步炬灭,生成 ID 的方法

func (n *Node) Generate() ID {
    
    n.mu.Lock() // 保證并發(fā)安全, 加鎖
    defer n.mu.Unlock() // 方法運行完畢后解鎖

    // 獲取當前時間的時間戳 (毫秒數顯示)
    now := time.Now().UnixNano() / 1e6

    if n.timestamp == now {
        // step 步進 1 
        n.step ++

        // 當前 step 用完
        if n.step > stepMax {
            // 等待本毫秒結束
            for now <= n.timestamp {
                now = time.Now().UnixNano() / 1e6
            }
        }

    } else {
        // 本毫秒內 step 用完
        n.step = 0
    }
    
    n.timestamp = now
    // 移位運算,生產最終 ID
    result := ID((now - Epoch) << timeShift | (n.node << nodeShift) | (n.step))

    return result
}

測試

我們使用循環(huán)去開啟多個 goroutine 去并發(fā)生成 ID靡菇,然后使用 map 以 ID 作為鍵存儲重归,來判斷是否生成了唯一的 ID

main 函數代碼

func main() {
    // 測試腳本

    // 生成節(jié)點實例
    node, err := NewNode(1)

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

    ch := make(chan ID)
    count := 10000
    // 并發(fā) count 個 goroutine 進行 snowflake ID 生成
    for i := 0; i < count; i++ {
        go func() {
            id := node.Generate()
            ch <- id
        }()
    }   
        
    defer close(ch)

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

完整的程序實例 :點我查看

上線使用

你可以用 go 的 net/http 包處理并發(fā)請求,生成 ID 并且返回 http 響應結果厦凤。
Just do it

參考文章

【1】理解分布式id生成算法SnowFlake
【2】bwmarrin/snowflake

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末鼻吮,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子较鼓,更是在濱河造成了極大的恐慌椎木,老刑警劉巖违柏,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異香椎,居然都是意外死亡漱竖,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門畜伐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馍惹,“玉大人,你說我怎么就攤上這事玛界⊥蚍” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵慎框,是天一觀的道長良狈。 經常有香客問我,道長鲤脏,這世上最難降的妖魔是什么们颜? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮猎醇,結果婚禮上窥突,老公的妹妹穿的比我還像新娘。我一直安慰自己硫嘶,他們只是感情好阻问,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沦疾,像睡著了一般称近。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哮塞,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天刨秆,我揣著相機與錄音,去河邊找鬼忆畅。 笑死衡未,一個胖子當著我的面吹牛,可吹牛的內容都是我干的家凯。 我是一名探鬼主播缓醋,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绊诲!你這毒婦竟也來了送粱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掂之,失蹤者是張志新(化名)和其女友劉穎抗俄,沒想到半個月后脆丁,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡橄镜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年偎快,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽胶。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖裆馒,靈堂內的尸體忽然破棺而出姊氓,到底是詐尸還是另有隱情,我是刑警寧澤喷好,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布翔横,位于F島的核電站,受9級特大地震影響梗搅,放射性物質發(fā)生泄漏禾唁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一无切、第九天 我趴在偏房一處隱蔽的房頂上張望荡短。 院中可真熱鬧,春花似錦哆键、人聲如沸掘托。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闪盔。三九已至,卻和暖如春辱士,著一層夾襖步出監(jiān)牢的瞬間泪掀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工颂碘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留异赫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓凭涂,卻偏偏與公主長得像祝辣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子切油,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內容