Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法
分布式唯一ID的生成
背景:
在分布式架構(gòu)下,唯一序列號(hào)生成是我們?cè)谠O(shè)計(jì)一個(gè)尤其是數(shù)據(jù)庫(kù)使用分庫(kù)分表的時(shí)候會(huì)常見(jiàn)的一個(gè)問(wèn)題
特性:
全局唯一,這是基本要求,不能出現(xiàn)重復(fù)數(shù)字類型,趨勢(shì)遞增,后面的ID必須比前面的大長(zhǎng)度短,能夠提高查詢效率,這也是從MySQL數(shù)據(jù)庫(kù)規(guī)范出發(fā)的,尤其是ID作為主鍵時(shí)**信息安全,**如果ID連續(xù)生成,勢(shì)必會(huì)泄露業(yè)務(wù)信息,所以需要無(wú)規(guī)則不規(guī)則高可用低延時(shí),ID生成快,能夠扛住高并發(fā),延時(shí)足夠低不至于成為業(yè)務(wù)瓶頸.
雪花算法:
? snowflake是推特開(kāi)源的分布式ID生成算法
結(jié)果: long 型的ID號(hào)(64位的ID號(hào))
核心思想(生成的ID號(hào)是64位那么就對(duì)64位進(jìn)行劃分賦予特別的含義):
41bit-時(shí)間戳決定了該算法生成ID號(hào)的可用年限.
10bit-工作機(jī)器編號(hào)決定了該分布式系統(tǒng)的擴(kuò)容性即機(jī)器數(shù)量.
12bit-序列號(hào)決定了每毫秒單機(jī)系統(tǒng)可以生成的序列號(hào)
拓展:什么是時(shí)間戳?
北京時(shí)間1970年01月01日08時(shí)00分00秒到此時(shí)時(shí)刻的總秒數(shù)
優(yōu)勢(shì):
//實(shí)現(xiàn)方法: package main import ( "errors" "fmt" "sync" "time" ) /* 雪花算法(snowFlake)的具體實(shí)現(xiàn)方案: */ type SnowFlake struct{ mu sync.Mutex //雪花算法開(kāi)啟時(shí)的起始時(shí)間戳 twepoch int64 //每一部分占用的位數(shù) workerIdBits int64 //每個(gè)數(shù)據(jù)中心的工作機(jī)器的編號(hào)位數(shù) datacenterIdBits int64 //數(shù)據(jù)中心的編號(hào)位數(shù) sequenceBits int64 //每個(gè)工作機(jī)器每毫秒遞增的位數(shù) //每一部分最大的數(shù)值 maxWorkerId int64 maxDatacenterId int64 maxSequence int64 //每一部分向左移動(dòng)的位數(shù) workerIdShift int64 datacenterIdShift int64 timestampShift int64 //當(dāng)前數(shù)據(jù)中心ID號(hào) datacenterId int64 //當(dāng)前機(jī)器的ID號(hào) workerId int64 //序列號(hào) sequence int64 //上一次生成ID號(hào)前41位的毫秒時(shí)間戳 lastTimestamp int64 } /* 獲取毫秒的時(shí)間戳 */ func (s *SnowFlake)timeGen()int64{ return time.Now().UnixMilli() } /* 獲取比lastTimestamp大的當(dāng)前毫秒時(shí)間戳 */ 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()//獲取當(dāng)前的毫秒級(jí)別的時(shí)間戳 if nowTimestamp<s.lastTimestamp{ //系統(tǒng)時(shí)鐘倒退,倒退了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中有一個(gè)循環(huán)等候當(dāng)前毫秒時(shí)間戳到達(dá)lastTimestamp的下一個(gè)毫秒時(shí)間戳 nowTimestamp=s.tilNextMills() } }else{ s.sequence=0 } s.lastTimestamp=nowTimestamp return (nowTimestamp-s.twepoch)<<s.timestampShift| //時(shí)間戳差值部分 s.datacenterId<<s.datacenterIdShift| //數(shù)據(jù)中心部分 s.workerId<<s.workerIdShift| //工作機(jī)器編號(hào)部分 s.sequence, //序列號(hào)部分 nil } func NewSnowFlake(workerId int64,datacenterId int64)(*SnowFlake,error){ mySnow:=new(SnowFlake) mySnow.twepoch=time.Now().Unix() //返回當(dāng)前時(shí)間的時(shí)間戳(時(shí)間戳是指北京時(shí)間1970年01月01日8時(shí)0分0秒到此時(shí)時(shí)刻的總秒數(shù)) if workerId<0||datacenterId<0{ return nil,errors.New("workerId or datacenterId must not lower than 0 ") } /* 標(biāo)準(zhǔn)的雪花算法 */ 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(){ //模擬實(shí)驗(yàn)是生成并發(fā)400W個(gè)ID,所需要的時(shí)間 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號(hào)只需要803.1006ms(所以單機(jī)上可以每秒生成的ID數(shù)在400W以上)
優(yōu)點(diǎn):
毫秒數(shù)在高位,自增序列在低位,整個(gè)ID都是趨勢(shì)遞增不依賴數(shù)據(jù)庫(kù)等第三方系統(tǒng),以服務(wù)的方式部署,穩(wěn)定性更高,生成的ID性能也是非常高的可以根據(jù)自身業(yè)務(wù)特性分配bit位,非常靈活
缺陷:
1. 依賴機(jī)器時(shí)鐘,如果**機(jī)器時(shí)鐘回?fù)?*,會(huì)導(dǎo)致重復(fù)ID生成.
2. 在單機(jī)上是遞增的,但是由于設(shè)計(jì)到分布式環(huán)境下,每臺(tái)機(jī)器上的時(shí)鐘不可能完全同步,有時(shí)候會(huì)出現(xiàn)不是全局遞增的情況.
如何解決單機(jī)系統(tǒng)中時(shí)鐘回?fù)軉?wèn)題:
? 可以分為兩種情況:
1. 如果**時(shí)間回?fù)軙r(shí)間較短,比如配置5ms以內(nèi)**,那么可以直接等候一定的時(shí)間,讓機(jī)器時(shí)間追上來(lái)
2. 如果**時(shí)間回?fù)軙r(shí)間較長(zhǎng)**,我們不能接收這么長(zhǎng)的阻塞等候,那么就有兩個(gè)策略,直接拒絕,拋出異常;或者通過(guò)RD時(shí)鐘回滾
布式環(huán)境下,每臺(tái)機(jī)器上的時(shí)鐘不可能完全同步,有時(shí)候會(huì)出現(xiàn)不是全局遞增的情況.
如何解決單機(jī)系統(tǒng)中時(shí)鐘回?fù)軉?wèn)題:
? 可以分為兩種情況:
1. 如果**時(shí)間回?fù)軙r(shí)間較短,比如配置5ms以內(nèi)**,那么可以直接等候一定的時(shí)間,讓機(jī)器時(shí)間追上來(lái)
2. 如果**時(shí)間回?fù)軙r(shí)間較長(zhǎng)**,我們不能接收這么長(zhǎng)的阻塞等候,那么就有兩個(gè)策略,直接拒絕,拋出異常;或者通過(guò)RD時(shí)鐘回滾
參考博客高并發(fā)情況下,雪花ID一秒400W個(gè),以及分布式ID算法(詳析)
到此這篇關(guān)于Go實(shí)現(xiàn)分布式唯一ID的生成之雪花算法的文章就介紹到這了,更多相關(guān)Go分布式唯一ID 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go 協(xié)程超時(shí)控制的實(shí)現(xiàn)
本文主要介紹了Go 協(xié)程超時(shí)控制的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn)
本文主要介紹了淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02基于Golang設(shè)計(jì)一套可控的定時(shí)任務(wù)系統(tǒng)
這篇文章主要為大家學(xué)習(xí)介紹了如何基于Golang設(shè)計(jì)一套可控的定時(shí)任務(wù)系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-07-07教你一分鐘配置好Go語(yǔ)言開(kāi)發(fā)環(huán)境(多種操作系統(tǒng))
在這篇文章中,我們從頭到尾一步步指導(dǎo)你配置Golang開(kāi)發(fā)環(huán)境,并編寫你的第一個(gè)"Hello,?World!"程序,我們?cè)敿?xì)解釋了在多種操作系統(tǒng)(包括Windows、Linux和macOS)下的安裝過(guò)程、環(huán)境變量設(shè)置以及如何驗(yàn)證安裝是否成功2023-09-09Golang如何調(diào)用windows下的dll動(dòng)態(tài)庫(kù)中的函數(shù)
這篇文章主要介紹了Golang如何調(diào)用windows下的dll動(dòng)態(tài)庫(kù)中的函數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05golang基于websocket通信tcp keepalive研究記錄
這篇文章主要為大家介紹了golang基于websocket通信tcp keepalive研究記錄,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go語(yǔ)言編程學(xué)習(xí)golang配置golint
這篇文章主要為大家介紹了Go語(yǔ)言編程學(xué)習(xí)golang配置golint的過(guò)程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11golang打包成帶圖標(biāo)的exe可執(zhí)行文件
這篇文章主要給大家介紹了關(guān)于golang打包成帶圖標(biāo)的exe可執(zhí)行文件的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-06-06