Go實現(xiàn)分布式唯一ID的生成之雪花算法
分布式唯一ID的生成
背景:
在分布式架構(gòu)下,唯一序列號生成是我們在設(shè)計一個尤其是數(shù)據(jù)庫使用分庫分表的時候會常見的一個問題
特性:
全局唯一,這是基本要求,不能出現(xiàn)重復(fù)數(shù)字類型,趨勢遞增,后面的ID必須比前面的大長度短,能夠提高查詢效率,這也是從MySQL數(shù)據(jù)庫規(guī)范出發(fā)的,尤其是ID作為主鍵時**信息安全,**如果ID連續(xù)生成,勢必會泄露業(yè)務(wù)信息,所以需要無規(guī)則不規(guī)則高可用低延時,ID生成快,能夠扛住高并發(fā),延時足夠低不至于成為業(yè)務(wù)瓶頸.
雪花算法:
? snowflake是推特開源的分布式ID生成算法
結(jié)果: long 型的ID號(64位的ID號)
核心思想(生成的ID號是64位那么就對64位進行劃分賦予特別的含義):

41bit-時間戳決定了該算法生成ID號的可用年限.
10bit-工作機器編號決定了該分布式系統(tǒng)的擴容性即機器數(shù)量.
12bit-序列號決定了每毫秒單機系統(tǒng)可以生成的序列號
拓展:什么是時間戳?
北京時間1970年01月01日08時00分00秒到此時時刻的總秒數(shù)
優(yōu)勢:
//實現(xiàn)方法:
package main
import (
"errors"
"fmt"
"sync"
"time"
)
/*
雪花算法(snowFlake)的具體實現(xiàn)方案:
*/
type SnowFlake struct{
mu sync.Mutex
//雪花算法開啟時的起始時間戳
twepoch int64
//每一部分占用的位數(shù)
workerIdBits int64 //每個數(shù)據(jù)中心的工作機器的編號位數(shù)
datacenterIdBits int64 //數(shù)據(jù)中心的編號位數(shù)
sequenceBits int64 //每個工作機器每毫秒遞增的位數(shù)
//每一部分最大的數(shù)值
maxWorkerId int64
maxDatacenterId int64
maxSequence int64
//每一部分向左移動的位數(shù)
workerIdShift int64
datacenterIdShift int64
timestampShift int64
//當前數(shù)據(jù)中心ID號
datacenterId int64
//當前機器的ID號
workerId int64
//序列號
sequence int64
//上一次生成ID號前41位的毫秒時間戳
lastTimestamp int64
}
/*
獲取毫秒的時間戳
*/
func (s *SnowFlake)timeGen()int64{
return time.Now().UnixMilli()
}
/*
獲取比lastTimestamp大的當前毫秒時間戳
*/
func (s *SnowFlake)tilNextMills()int64{
timeStampMill:=s.timeGen()
for timeStampMill<=s.lastTimestamp{
timeStampMill=s.timeGen()
}
return timeStampMill
}
func (s *SnowFlake)NextId()(int64,error){
s.mu.Lock()
defer s.mu.Unlock()
nowTimestamp:=s.timeGen()//獲取當前的毫秒級別的時間戳
if nowTimestamp<s.lastTimestamp{
//系統(tǒng)時鐘倒退,倒退了s.lastTimestamp-nowTimestamp
return -1,errors.New(fmt.Sprintf("clock moved backwards, Refusing to generate id for %d milliseconds",s.lastTimestamp-nowTimestamp))
}
if nowTimestamp==s.lastTimestamp{
s.sequence=(s.sequence+1)&s.maxSequence
if s.sequence==0{
//tilNextMills中有一個循環(huán)等候當前毫秒時間戳到達lastTimestamp的下一個毫秒時間戳
nowTimestamp=s.tilNextMills()
}
}else{
s.sequence=0
}
s.lastTimestamp=nowTimestamp
return (nowTimestamp-s.twepoch)<<s.timestampShift| //時間戳差值部分
s.datacenterId<<s.datacenterIdShift| //數(shù)據(jù)中心部分
s.workerId<<s.workerIdShift| //工作機器編號部分
s.sequence, //序列號部分
nil
}
func NewSnowFlake(workerId int64,datacenterId int64)(*SnowFlake,error){
mySnow:=new(SnowFlake)
mySnow.twepoch=time.Now().Unix() //返回當前時間的時間戳(時間戳是指北京時間1970年01月01日8時0分0秒到此時時刻的總秒數(shù))
if workerId<0||datacenterId<0{
return nil,errors.New("workerId or datacenterId must not lower than 0 ")
}
/*
標準的雪花算法
*/
mySnow.workerIdBits =5
mySnow.datacenterIdBits=5
mySnow.sequenceBits=12
mySnow.maxWorkerId=-1^(-1<<mySnow.workerIdBits) //64位末尾workerIdBits位均設(shè)為1,其余設(shè)為0
mySnow.maxDatacenterId=-1^(-1<<mySnow.datacenterIdBits) //64位末尾datacenterIdBits位均設(shè)為1,其余設(shè)為0
mySnow.maxSequence=-1^(-1<<mySnow.sequenceBits) //64位末尾sequenceBits位均設(shè)為1,其余設(shè)為0
if workerId>=mySnow.maxWorkerId||datacenterId>=mySnow.maxDatacenterId{
return nil,errors.New("workerId or datacenterId must not higher than max value ")
}
mySnow.workerIdShift=mySnow.sequenceBits
mySnow.datacenterIdShift=mySnow.sequenceBits+mySnow.workerIdBits
mySnow.timestampShift=mySnow.sequenceBits+mySnow.workerIdBits+mySnow.datacenterIdBits
mySnow.lastTimestamp=-1
mySnow.workerId=workerId
mySnow.datacenterId=datacenterId
return mySnow,nil
}
func main(){
//模擬實驗是生成并發(fā)400W個ID,所需要的時間
mySnow,_:=NewSnowFlake(0,0)//生成雪花算法
group:=sync.WaitGroup{}
startTime:=time.Now()
generateId:=func (s SnowFlake,requestNumber int){
for i:=0;i<requestNumber;i++{
s.NextId()
group.Done()
}
}
group.Add(4000000)
//生成并發(fā)的數(shù)為4000000
currentThreadNum:=400
for i:=0;i<currentThreadNum;i++{
generateId(*mySnow,10000)
}
group.Wait()
fmt.Printf("time: %v\n",time.Now().Sub(startTime))
}

以上分析生成400WID號只需要803.1006ms(所以單機上可以每秒生成的ID數(shù)在400W以上)
優(yōu)點:
毫秒數(shù)在高位,自增序列在低位,整個ID都是趨勢遞增不依賴數(shù)據(jù)庫等第三方系統(tǒng),以服務(wù)的方式部署,穩(wěn)定性更高,生成的ID性能也是非常高的可以根據(jù)自身業(yè)務(wù)特性分配bit位,非常靈活
缺陷:
1. 依賴機器時鐘,如果**機器時鐘回撥**,會導(dǎo)致重復(fù)ID生成.
2. 在單機上是遞增的,但是由于設(shè)計到分布式環(huán)境下,每臺機器上的時鐘不可能完全同步,有時候會出現(xiàn)不是全局遞增的情況.
如何解決單機系統(tǒng)中時鐘回撥問題:
? 可以分為兩種情況:
1. 如果**時間回撥時間較短,比如配置5ms以內(nèi)**,那么可以直接等候一定的時間,讓機器時間追上來
2. 如果**時間回撥時間較長**,我們不能接收這么長的阻塞等候,那么就有兩個策略,直接拒絕,拋出異常;或者通過RD時鐘回滾
布式環(huán)境下,每臺機器上的時鐘不可能完全同步,有時候會出現(xiàn)不是全局遞增的情況.
如何解決單機系統(tǒng)中時鐘回撥問題:
? 可以分為兩種情況:
1. 如果**時間回撥時間較短,比如配置5ms以內(nèi)**,那么可以直接等候一定的時間,讓機器時間追上來
2. 如果**時間回撥時間較長**,我們不能接收這么長的阻塞等候,那么就有兩個策略,直接拒絕,拋出異常;或者通過RD時鐘回滾
參考博客高并發(fā)情況下,雪花ID一秒400W個,以及分布式ID算法(詳析)
到此這篇關(guān)于Go實現(xiàn)分布式唯一ID的生成之雪花算法的文章就介紹到這了,更多相關(guān)Go分布式唯一ID 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Golang設(shè)計一套可控的定時任務(wù)系統(tǒng)
這篇文章主要為大家學習介紹了如何基于Golang設(shè)計一套可控的定時任務(wù)系統(tǒng),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-07-07
教你一分鐘配置好Go語言開發(fā)環(huán)境(多種操作系統(tǒng))
在這篇文章中,我們從頭到尾一步步指導(dǎo)你配置Golang開發(fā)環(huán)境,并編寫你的第一個"Hello,?World!"程序,我們詳細解釋了在多種操作系統(tǒng)(包括Windows、Linux和macOS)下的安裝過程、環(huán)境變量設(shè)置以及如何驗證安裝是否成功2023-09-09
Golang如何調(diào)用windows下的dll動態(tài)庫中的函數(shù)
這篇文章主要介紹了Golang如何調(diào)用windows下的dll動態(tài)庫中的函數(shù)方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05
golang基于websocket通信tcp keepalive研究記錄
這篇文章主要為大家介紹了golang基于websocket通信tcp keepalive研究記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06

