Go 實現雪花演算法

JaguarJack發表於2019-04-05

雪花演算法

Twitter 的 SnowFlake 是一種經典分散式 ID 生成演算法。ID 是一個 64 位的長整型。

file

  • 1位,不用。二進位制中最高位為1的都是負數,但是我們生成的id一般都使用整數,所以這個最高位固定是0
  • 41位,用來記錄時間戳(毫秒)。

41位可以表示241−1個數字,

  • 如果只用來表示正整數(計算機中正數包含0),可以表示的數值範圍是:0 至 241−1,減1是因為可表示的數值範圍是從0開始算的,而不是1。\
    也就是說41位可以表示241−1個毫秒的值,轉化成單位年則是(241−1)/(1000∗60∗60∗24∗365)=69年

10位,用來記錄工作機器id。

可以部署在210=1024個節點,包括5位datacenterId和5位workerId\
5位(bit)可以表示的最大正整數是25−1=31,即可以用0、1、2、3、....31這32個數字,來表示不同的datecenterId或workerId

12位,序列號,用來記錄同毫秒內產生的不同id。

12位(bit)可以表示的最大正整數是212−1=4095,即可以用0、1、2、3、....4094這4095個數字,來表示同一機器同一時間截(毫秒)內產生的4095個ID序號

// Fetch prints the content found at a URL.
package main

import (
    "fmt"
    "log"
    "sync"
    "time"
)
const (
    workerIdBits int64  = 5
    datacenterIdBits int64 = 5
    sequenceBits int64 = 12

    maxWorkerId int64 = -1 ^ (-1 << uint64(workerIdBits))
    maxDatacenterId int64 = -1 ^ (-1 << uint64(datacenterIdBits))
    maxSequence int64 = -1 ^ (-1 << uint64(sequenceBits))

    timeLeft uint8 = 22
    dataLeft uint8 = 17
    workLeft uint8 = 12

    twepoch int64 = 1525705533000
)

type  worker struct {
    mu        sync.Mutex
    laststamp int64
    workerid int64
    datacenterid int64
    sequence int64
}

func(w *worker) getCurrentTime() int64 {
    return time.Now().UnixNano() / 1e6
}
//var i int = 1
func(w *worker) nextId() int64 {
    w.mu.Lock()
    defer w.mu.Unlock()
    timestamp := w.getCurrentTime()
    if timestamp < w.laststamp {
        log.Fatal("can not generate id")
    }
    if w.laststamp == timestamp {
    // 這其實和 <==>
        // w.sequence++
        // if w.sequence++ > maxSequence  等價
        w.sequence = (w.sequence + 1) & maxSequence
        if w.sequence == 0 {
        // 之前使用 if, 只是沒想到 GO 可以在一毫秒以內能生成到最大的 Sequence, 那樣就會導致很多重複的
            // 這個地方使用 for 來等待下一毫秒
            for timestamp <= w.laststamp {
                //i++
                //fmt.Println(i)
                timestamp = w.getCurrentTime()
            }
        }
    } else {
        w.sequence = 0
    }
    w.laststamp = timestamp

    return ((timestamp - twepoch) << timeLeft) | (w.datacenterid << dataLeft)  | (w.workerid << workLeft) | w.sequence
}
func (w *worker) tilNextMillis() int64 {
    timestamp := w.getCurrentTime()
    if (timestamp <= w.laststamp) {
        timestamp = w.getCurrentTime()
    }
    return timestamp
}

func main() {
    w := new(worker)
    // 上一次時間
    w.laststamp = -1
    w.workerid  = 10
    w.datacenterid = 12
    w.sequence = 14

    i := 0
    r := make([]int64, 0)
    for {
        id := w.nextId()
        r = append(r, id)
        i++
        if  i > 10000000 {
            break
        }
    }
    j := 0
    for _,v := range r {
        if v > 1 {}
        j++
    }
    fmt.Println(j)
    fmt.Println(len(unique(r)))
    fmt.Println(w)

}
func unique(m[]int64) []int64 {
    s := make([]int64, 0)
    smap := make(map[int64]int64)
    for _, value := range m {
        //計算map長度
        length := len(smap)
        smap[value] = 1
        //比較map長度, 如果map長度不相等, 說明key不存在
        if len(smap) != length {
            s = append(s, value)
        }
    }

    return  s

}

程式碼解釋

    maxSequence int64 = -1 ^ (-1 << uint64(sequenceBits))

負數的二進位制

這設計兩個名詞,反碼和補碼。來看一下整型 1 的二進位制原碼。

00000000 00000000 00000000 00000001

反碼 (二進位制取反)

11111111 11111111 11111111 11111110

補碼 (反碼 + 1)

11111111 11111111 11111111 11111111

再看這段程式碼,轉成二進位制:

11111111 11111111 11111111 11111111 ^ (11111111 11111111 11111111 11111111 << 12)\
11111111 11111111 11111111 11111111 ^ 11111111 11111111 11110000 00000000\
00000000 00000000 00001111 11111111 => 4095

整個過程大概就是這樣的\
還有就是 按位或 (|) 和 按位異或(^)的區別

0011 | 1011 => 1011 (按照征程 || 來想就可以了,1 true 0 false)\
0011 ^ 1011 => 1000 (位相同為零, 位不同為一)

最後從結果來看,生成 1000W,也沒有重複的。 而且速度也很快,個人感知大概在兩秒左右。

相關文章