[開(kāi)源閱讀]snowflake-Go

github地址:https://github.com/bwmarrin/snowflake

總共就300行代碼钳宪,主要邏輯也就100行吧碍遍。

這是一款大數(shù)據(jù)量下的id生成器的snowflake-golang實(shí)現(xiàn),snowflake生成的id (int64類(lèi)型)包含毫秒時(shí)間戳尊浓、機(jī)器id歼冰、同一毫秒下的自增id這3部分?jǐn)?shù)據(jù),這里面主要是位運(yùn)算的妙用(好多開(kāi)源項(xiàng)目都會(huì)用到位運(yùn)算)

用int64的64bit存儲(chǔ)以下部分:

  • 12bit的自增id step(同一毫秒下)
  • 10bit的機(jī)器id node(多臺(tái)機(jī)器)
  • 41bit的毫秒時(shí)間間距 time(不用從1970開(kāi)始算)
  • 1bit的unset 預(yù)留

這幾個(gè)參數(shù),12bit 10bit 41bit 1bit其實(shí)都可以根據(jù)自己情況自定義:

  • 41bit的毫秒time最多可以表示((1<<41)-1) / (86400*1000*465) = 69.7年(減1是因?yàn)榘?)
  • 10bit的node最多可表示(1<<10)-1=1023個(gè)機(jī)器
  • 12bit的step同一毫秒最多可表示(1<<12)-1=4095個(gè)自增id (同一機(jī)器同一毫秒生產(chǎn)的id數(shù)目大于4095怎么辦酒请,代碼就體現(xiàn)了)

看看怎么用:

    // Create a new Node with a Node number of 1
    node, err := snowflake.NewNode(1)
    if err != nil {
        fmt.Println(err)
        return
    }

    // Generate a snowflake ID.
    id := node.Generate()

    // Print out the ID's timestamp
    fmt.Printf("ID Time  : %d\n", id.Time())

    // Print out the ID's node number
    fmt.Printf("ID Node  : %d\n", id.Node())

    // Print out the ID's sequence number
    fmt.Printf("ID Step  : %d\n", id.Step())

先來(lái)生成id

不過(guò)生成id前,先new一個(gè)node對(duì)象

func NewNode(node int64) (*Node, error) {

    n := Node{}
    n.node = node
    n.nodeMax = -1 ^ (-1 << NodeBits) // == (1<< NodeBits) - 1
    n.nodeMask = n.nodeMax << StepBits // nodeMask以及stepMask主要用來(lái)由生成的id反推node和step
    n.stepMask = -1 ^ (-1 << StepBits) // == (1<< StepBits) - 1
    n.timeShift = NodeBits + StepBits // time右移NodeBits + StepBits才是id中time對(duì)應(yīng)的位置
    n.nodeShift = StepBits // node右移StepBits才是id中node對(duì)應(yīng)的位置
    // step是從0位到12位

    if n.node < 0 || n.node > n.nodeMax {
        return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
    }

    var curTime = time.Now()
    // add time.Duration to curTime to make sure we use the monotonic clock if available
    n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))

    return &n, nil
}

以上的StepBits, NodeBits, Epoch都是配置項(xiàng)

  • StepBits = 12
  • NodeBits = 10
  • Epoch = 1288834974657 (毫秒時(shí)間戳,這里表示的是2010年)

注意Epoch不用從1970開(kāi)始算,總共才有41bit表示毫秒時(shí)間,從1970開(kāi)始有點(diǎn)浪費(fèi),可以設(shè)置為距項(xiàng)目上線(xiàn)時(shí)間最近的時(shí)間,可以持續(xù)69年生成id身诺。如果從1970年開(kāi)始算,41bit還可以存儲(chǔ)18年的毫秒時(shí)間

正式生成id

func (n *Node) Generate() ID {

    n.mu.Lock()

    // nanoseconds 1e9
    // now單位毫秒
    now := time.Since(n.epoch).Nanoseconds() / 1000000

    // 每毫秒可以產(chǎn)生n.stepMask個(gè)id
    // n.step的值[0, n.stepMask]
    if now == n.time {
        n.step = (n.step + 1) & n.stepMask

        // 當(dāng)1毫秒產(chǎn)生的id個(gè)數(shù)大于n.stepMask時(shí)
        if n.step == 0 {
            // 強(qiáng)制sleep直到下一毫秒
            for now <= n.time {
                now = time.Since(n.epoch).Nanoseconds() / 1000000
            }
        }
    } else {
        // 當(dāng)前這一毫秒還沒(méi)有生成id,用0即可
        n.step = 0
    }

    n.time = now

    // 所以r由3部分組成: time node step
    // shift表示位移量
    // 或操作 只要對(duì)應(yīng)位有1個(gè)為1就為1抄囚,方便由r反推time node step
    r := ID((now)<<n.timeShift |
        (n.node << n.nodeShift) |
        (n.step),
    )

    n.mu.Unlock()
    return r
}

所以上面的問(wèn)題霉赡,同一臺(tái)機(jī)器1ms生成的id數(shù)大于4095就是死循環(huán)直到下一個(gè)ms.

由id反推time node step

func (f ID) Time() int64 {
    return (int64(f) >> timeShift) + Epoch
}

func (f ID) Node() int64 {
    // 位運(yùn)算優(yōu)先級(jí)高
    return int64(f) & nodeMask >> nodeShift
}

func (f ID) Step() int64 {
    // f的后stepBits位為step
    // stepMask為step所占用的stepBits個(gè)位的最大值
    // 與運(yùn)算結(jié)果的最大值為stepMask
    return int64(f) & stepMask
}

生成id時(shí)用的或運(yùn)算,反推用與運(yùn)算幔托。
本菜鳥(niǎo)切實(shí)體會(huì)到了位運(yùn)算的精妙

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穴亏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子重挑,更是在濱河造成了極大的恐慌嗓化,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬哀,死亡現(xiàn)場(chǎng)離奇詭異刺覆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)史煎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)谦屑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人篇梭,你說(shuō)我怎么就攤上這事氢橙。” “怎么了恬偷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵悍手,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)坦康,這世上最難降的妖魔是什么竣付? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮涝焙,結(jié)果婚禮上卑笨,老公的妹妹穿的比我還像新娘孕暇。我一直安慰自己仑撞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布妖滔。 她就那樣靜靜地躺著隧哮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪座舍。 梳的紋絲不亂的頭發(fā)上沮翔,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音曲秉,去河邊找鬼采蚀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛承二,可吹牛的內(nèi)容都是我干的榆鼠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼亥鸠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼妆够!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起负蚊,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤神妹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后家妆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鸵荠,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年伤极,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛹找。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡塑荒,死狀恐怖熄赡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情齿税,我是刑警寧澤彼硫,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響拧篮,放射性物質(zhì)發(fā)生泄漏词渤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一串绩、第九天 我趴在偏房一處隱蔽的房頂上張望缺虐。 院中可真熱鬧,春花似錦礁凡、人聲如沸高氮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剪芍。三九已至,卻和暖如春窟蓝,著一層夾襖步出監(jiān)牢的瞬間罪裹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工运挫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留状共,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓谁帕,卻偏偏與公主長(zhǎng)得像峡继,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雇卷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354