欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言實(shí)現(xiàn)Snowflake雪花算法

 更新時(shí)間:2021年06月08日 09:38:50   作者:luozhiyun  
雪花算法產(chǎn)生的背景當(dāng)然是twitter高并發(fā)環(huán)境下對(duì)唯一ID生成的需求,得益于twitter內(nèi)部牛的技術(shù),雪花算法能夠流傳于至今并且被廣泛使用,本文就詳細(xì)的介紹一下,感興趣的可以了解一下

每次放長(zhǎng)假的在家里的時(shí)候,總想找點(diǎn)簡(jiǎn)單的例子來(lái)看看實(shí)現(xiàn)原理,這次我們來(lái)看看 Go 語(yǔ)言雪花算法。

介紹

有時(shí)候在業(yè)務(wù)中,需要使用一些唯一的ID,來(lái)記錄我們某個(gè)數(shù)據(jù)的標(biāo)識(shí)。最常用的無(wú)非以下幾種:UUID、數(shù)據(jù)庫(kù)自增主鍵、Redis的Incr命令等方法來(lái)獲取一個(gè)唯一的值。下面我們分別說(shuō)一下它們的優(yōu)劣,以便引出我們的分布式雪花算法。

雪花算法

雪花算法的原始版本是scala版,用于生成分布式ID(純數(shù)字,時(shí)間順序),訂單編號(hào)等。

自增ID:對(duì)于數(shù)據(jù)敏感場(chǎng)景不宜使用,且不適合于分布式場(chǎng)景。 GUID:采用無(wú)意義字符串,數(shù)據(jù)量增大時(shí)造成訪問(wèn)過(guò)慢,且不宜排序。

UUID

首先是 UUID ,它是由128位二進(jìn)制組成,一般轉(zhuǎn)換成十六進(jìn)制,然后用String表示。為了保證 UUID 的唯一性,規(guī)范定義了包括網(wǎng)卡MAC地址、時(shí)間戳、名字空(Namespace)、隨機(jī)或偽隨機(jī)數(shù)、時(shí)序等元素,以及從這些元素生成 UUID 的算法。

UUID 有五個(gè)版本:

  • 版本1:基于時(shí)間戳和mac地址
  • 版本2:基于時(shí)間戳,mac地址和POSIX UID/GID
  • 版本3:基于MD5哈希算法
  • 版本4:基于隨機(jī)數(shù)
  • 版本5:基于SHA-1哈希算法

UUID 的優(yōu)缺點(diǎn):

優(yōu)點(diǎn)是代碼簡(jiǎn)單,性能比較好。缺點(diǎn)是沒(méi)有排序,無(wú)法保證按序遞增;其次是太長(zhǎng)了,存儲(chǔ)數(shù)據(jù)庫(kù)占用空間比較大,不利于檢索和排序。

數(shù)據(jù)庫(kù)自增主鍵

如果是使用 mysql 數(shù)據(jù)庫(kù),那么通過(guò)設(shè)置主鍵為 auto_increment 是最容易實(shí)現(xiàn)單調(diào)遞增的唯一ID 的方法,并且它也方便排序和索引。

但是缺點(diǎn)也很明顯,由于過(guò)度依賴(lài)數(shù)據(jù)庫(kù),那么受限于數(shù)據(jù)庫(kù)的性能會(huì)導(dǎo)致并發(fā)性并不高;再來(lái)就是如果數(shù)據(jù)量太大那么會(huì)給分庫(kù)分表會(huì)帶來(lái)問(wèn)題;并且如果數(shù)據(jù)庫(kù)宕機(jī)了,那么這個(gè)功能是無(wú)法使用的。

Redis

Redis 目前已在很多項(xiàng)目中是一個(gè)不可或缺的存在,在 Redis 中有兩個(gè)命令 Incr、IncrBy ,因?yàn)镽edis是單線程的所以通過(guò)這兩個(gè)指令可以能保證原子性從而達(dá)到生成唯一值的目標(biāo),并且性能也很好。

但是在 Redis 中,即使有 AOF 和 RDB ,但是依然會(huì)存在數(shù)據(jù)丟失,有可能會(huì)造成ID重復(fù);再來(lái)就是需要依賴(lài) Redis ,如果它不穩(wěn)定,那么會(huì)影響 ID 生成。

Snowflake

通過(guò)上面的一個(gè)個(gè)分析,終于引出了我們的分布式雪花算法 Snowflake ,它最早是twitter內(nèi)部使用的分布式環(huán)境下的唯一ID生成算法。在2014年開(kāi)源。開(kāi)源的版本由scala編寫(xiě),大家可以再找個(gè)地址找到這版本。

https://github.com/twitter-archive/snowflake/tags

它有以下幾個(gè)特點(diǎn):

  • 能滿(mǎn)足高并發(fā)分布式系統(tǒng)環(huán)境下ID不重復(fù);
  • 基于時(shí)間戳,可以保證基本有序遞增;
  • 不依賴(lài)于第三方的庫(kù)或者中間件;

實(shí)現(xiàn)原理

Snowflake 結(jié)構(gòu)是一個(gè) 64bit 的 int64 類(lèi)型的數(shù)據(jù)。如下:

位置大小作用
0~11bit12bits序列號(hào),用來(lái)對(duì)同一個(gè)毫秒之內(nèi)產(chǎn)生不同的ID,可記錄4095個(gè)
12~21bit10bits10bit用來(lái)記錄機(jī)器ID,總共可以記錄1024臺(tái)機(jī)器
22~62bit41bits用來(lái)記錄時(shí)間戳,這里可以記錄69年
63bit1bit符號(hào)位,不做處理

上面只是一個(gè)將 64bit 劃分的通用標(biāo)準(zhǔn),一般的情況可以根據(jù)自己的業(yè)務(wù)情況進(jìn)行調(diào)整。例如目前業(yè)務(wù)只有機(jī)器10臺(tái)左右預(yù)計(jì)未來(lái)會(huì)增加到三位數(shù),并且需要進(jìn)行多機(jī)房部署,QPS 幾年之內(nèi)會(huì)發(fā)展到百萬(wàn)。

那么對(duì)于百萬(wàn) QPS 平分到 10 臺(tái)機(jī)器上就是每臺(tái)機(jī)器承擔(dān)十萬(wàn)級(jí)的請(qǐng)求即可,12 bit 的序列號(hào)完全夠用。對(duì)于未來(lái)會(huì)增加到三位數(shù)機(jī)器,并且需要多機(jī)房部署的需求我們僅需要將 10 bits 的 work id 進(jìn)行拆分,分割 3 bits 來(lái)代表機(jī)房數(shù)共代表可以部署8個(gè)機(jī)房,其他 7bits 代表機(jī)器數(shù)代表每個(gè)機(jī)房可以部署128臺(tái)機(jī)器。那么數(shù)據(jù)格式就會(huì)如下所示:

代碼實(shí)現(xiàn)

實(shí)現(xiàn)步驟

其實(shí)看懂了上面的數(shù)據(jù)結(jié)構(gòu)之后,需要自己實(shí)現(xiàn)一個(gè)雪花算法是非常簡(jiǎn)單,步驟大致如下:

  • 獲取當(dāng)前的毫秒時(shí)間戳;
  • 用當(dāng)前的毫秒時(shí)間戳和上次保存的時(shí)間戳進(jìn)行比較;
    • 如果和上次保存的時(shí)間戳相等,那么對(duì)序列號(hào) sequence 加一;
    • 如果不相等,那么直接設(shè)置 sequence 為 0 即可;
  • 然后通過(guò)或運(yùn)算拼接雪花算法需要返回的 int64 返回值。

代碼實(shí)現(xiàn)

首先我們需要定義一個(gè) Snowflake 結(jié)構(gòu)體:

type Snowflake struct {
 sync.Mutex     // 鎖
 timestamp    int64 // 時(shí)間戳 ,毫秒
 workerid     int64  // 工作節(jié)點(diǎn)
 datacenterid int64 // 數(shù)據(jù)中心機(jī)房id
 sequence     int64 // 序列號(hào)
}

然后我們需要定義一些常量,方便我們?cè)谑褂醚┗ㄋ惴ǖ臅r(shí)候進(jìn)行位運(yùn)算取值:

const (
  epoch             = int64(1577808000000)                           // 設(shè)置起始時(shí)間(時(shí)間戳/毫秒):2020-01-01 00:00:00,有效期69年
 timestampBits     = uint(41)                                       // 時(shí)間戳占用位數(shù)
 datacenteridBits  = uint(2)                                        // 數(shù)據(jù)中心id所占位數(shù)
 workeridBits      = uint(7)                                        // 機(jī)器id所占位數(shù)
 sequenceBits      = uint(12)                                       // 序列所占的位數(shù)
 timestampMax      = int64(-1 ^ (-1 << timestampBits))              // 時(shí)間戳最大值
 datacenteridMax   = int64(-1 ^ (-1 << datacenteridBits))           // 支持的最大數(shù)據(jù)中心id數(shù)量
 workeridMax       = int64(-1 ^ (-1 << workeridBits))               // 支持的最大機(jī)器id數(shù)量
 sequenceMask      = int64(-1 ^ (-1 << sequenceBits))               // 支持的最大序列id數(shù)量
 workeridShift     = sequenceBits                                   // 機(jī)器id左移位數(shù)
 datacenteridShift = sequenceBits + workeridBits                    // 數(shù)據(jù)中心id左移位數(shù)
 timestampShift    = sequenceBits + workeridBits + datacenteridBits // 時(shí)間戳左移位數(shù)
)

需要注意的是由于 -1 在二進(jìn)制上表示是:

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

所以想要求得 41bits 的 timestamp 最大值可以將 -1 向左位移 41 位,得到:

11111111 11111111 11111110 00000000 00000000 00000000 00000000 00000000

那么再和 -1 進(jìn)行 ^異或運(yùn)算:

00000000 00000000 00000001 11111111 11111111 11111111 11111111 11111111

這就可以表示 41bits 的 timestamp 最大值。datacenteridMax、workeridMax也同理。

接著我們就可以設(shè)置一個(gè) NextVal 函數(shù)來(lái)獲取 Snowflake 返回的 ID 了:

func (s *Snowflake) NextVal() int64 {
 s.Lock()
 now := time.Now().UnixNano() / 1000000 // 轉(zhuǎn)毫秒
 if s.timestamp == now {
  // 當(dāng)同一時(shí)間戳(精度:毫秒)下多次生成id會(huì)增加序列號(hào)
  s.sequence = (s.sequence + 1) & sequenceMask
  if s.sequence == 0 {
   // 如果當(dāng)前序列超出12bit長(zhǎng)度,則需要等待下一毫秒
   // 下一毫秒將使用sequence:0
   for now <= s.timestamp {
    now = time.Now().UnixNano() / 1000000
   }
  }
 } else {
  // 不同時(shí)間戳(精度:毫秒)下直接使用序列號(hào):0
  s.sequence = 0
 }
 t := now - epoch
 if t > timestampMax {
  s.Unlock()
  glog.Errorf("epoch must be between 0 and %d", timestampMax-1)
  return 0
 }
 s.timestamp = now
 r := int64((t)<<timestampShift | (s.datacenterid << datacenteridShift) | (s.workerid << workeridShift) | (s.sequence))
 s.Unlock()
 return r
}

上面的代碼也是非常的簡(jiǎn)單,看看注釋?xiě)?yīng)該也能懂,我這里說(shuō)說(shuō)最后返回的 r 系列的位運(yùn)算表示什么意思。

首先 t 表示的是現(xiàn)在距離 epoch 的時(shí)間差,我們 epoch 在初始化的時(shí)候設(shè)置的是2020-01-01 00:00:00,那么對(duì)于 41bit 的 timestamp 來(lái)說(shuō)會(huì)在 69 年之后才溢出。對(duì) t 進(jìn)行向左位移之后,低于 timestampShift 位置上全是0 ,由 datacenterid、workerid、sequence 進(jìn)行取或填充。

在當(dāng)前的例子中,如果當(dāng)前時(shí)間是2021/01/01 00:00:00,那么位運(yùn)算結(jié)果如下圖所示:

到此這篇關(guān)于Go語(yǔ)言實(shí)現(xiàn)Snowflake雪花算法的文章就介紹到這了,更多相關(guān)Go語(yǔ)言雪花算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言?Channel通道詳解

    Go語(yǔ)言?Channel通道詳解

    Channel是一個(gè)通道,可以通過(guò)它讀取和寫(xiě)入數(shù)據(jù),它就像水管一樣,網(wǎng)絡(luò)數(shù)據(jù)通過(guò)Channel 讀取和寫(xiě)入,這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言?Channel通道的相關(guān)資料,需要的朋友可以參考下
    2023-07-07
  • golang協(xié)程池設(shè)計(jì)詳解

    golang協(xié)程池設(shè)計(jì)詳解

    這篇文章主要介紹了golang協(xié)程池設(shè)計(jì)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Go語(yǔ)言七篇入門(mén)教程五文件及包

    Go語(yǔ)言七篇入門(mén)教程五文件及包

    本章節(jié)主要介紹go語(yǔ)言的文件處理與包管理,本文是Go語(yǔ)言七篇入門(mén)教程系列篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-11-11
  • go語(yǔ)言中fallthrough的用法說(shuō)明

    go語(yǔ)言中fallthrough的用法說(shuō)明

    這篇文章主要介紹了go語(yǔ)言中fallthrough的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • Go語(yǔ)言使用defer+recover解決panic導(dǎo)致程序崩潰的問(wèn)題

    Go語(yǔ)言使用defer+recover解決panic導(dǎo)致程序崩潰的問(wèn)題

    如果協(xié)程出現(xiàn)了panic,就會(huì)造成程序的崩潰,這時(shí)可以在goroutine中使用recover來(lái)捕獲panic,進(jìn)行處理,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-09-09
  • golang實(shí)現(xiàn)基于channel的通用連接池詳解

    golang實(shí)現(xiàn)基于channel的通用連接池詳解

    這篇文章主要給大家介紹了關(guān)于golang實(shí)現(xiàn)基于channel的通用連接池的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-02-02
  • Golang算法問(wèn)題之整數(shù)拆分實(shí)現(xiàn)方法分析

    Golang算法問(wèn)題之整數(shù)拆分實(shí)現(xiàn)方法分析

    這篇文章主要介紹了Golang算法問(wèn)題之整數(shù)拆分實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言數(shù)值運(yùn)算與數(shù)組遍歷相關(guān)操作技巧,需要的朋友可以參考下
    2017-02-02
  • 簡(jiǎn)單四步快速集成go環(huán)境變量

    簡(jiǎn)單四步快速集成go環(huán)境變量

    這篇文章主要為大家介紹了快速集成go環(huán)境變量的簡(jiǎn)單四個(gè)步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Go中使用gjson來(lái)操作JSON數(shù)據(jù)的實(shí)現(xiàn)

    Go中使用gjson來(lái)操作JSON數(shù)據(jù)的實(shí)現(xiàn)

    本文主要介紹了Go中使用gjson來(lái)操作JSON數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 詳解Go語(yǔ)言中上下文context的理解與使用

    詳解Go語(yǔ)言中上下文context的理解與使用

    在Go的日常開(kāi)發(fā)中,Context上下文對(duì)象無(wú)處不在,這篇文章小編就來(lái)帶大家深入了解一下上下文context的理解與使用,文中的示例代碼講解詳細(xì),需要的可以參考下
    2023-10-10

最新評(píng)論