網站首頁 編程語言 正文
分布式唯一ID的生成
背景:
在分布式架構下,唯一序列號生成是我們在設計一個尤其是數據庫使用分庫分表的時候會常見的一個問題
特性:
全局唯一,這是基本要求,不能出現重復數字類型,趨勢遞增,后面的ID必須比前面的大長度短,能夠提高查詢效率,這也是從MySQL數據庫規范出發的,尤其是ID作為主鍵時**信息安全,**如果ID連續生成,勢必會泄露業務信息,所以需要無規則不規則高可用低延時,ID生成快,能夠扛住高并發,延時足夠低不至于成為業務瓶頸.
雪花算法:
? snowflake是推特開源的分布式ID生成算法
結果: long 型的ID號(64位的ID號)
核心思想(生成的ID號是64位那么就對64位進行劃分賦予特別的含義):
41bit-時間戳決定了該算法生成ID號的可用年限.
10bit-工作機器編號決定了該分布式系統的擴容性即機器數量.
12bit-序列號決定了每毫秒單機系統可以生成的序列號
拓展:什么是時間戳?
北京時間1970年01月01日08時00分00秒到此時時刻的總秒數
優勢:
//實現方法: package main import ( "errors" "fmt" "sync" "time" ) /* 雪花算法(snowFlake)的具體實現方案: */ type SnowFlake struct{ mu sync.Mutex //雪花算法開啟時的起始時間戳 twepoch int64 //每一部分占用的位數 workerIdBits int64 //每個數據中心的工作機器的編號位數 datacenterIdBits int64 //數據中心的編號位數 sequenceBits int64 //每個工作機器每毫秒遞增的位數 //每一部分最大的數值 maxWorkerId int64 maxDatacenterId int64 maxSequence int64 //每一部分向左移動的位數 workerIdShift int64 datacenterIdShift int64 timestampShift int64 //當前數據中心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{ //系統時鐘倒退,倒退了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中有一個循環等候當前毫秒時間戳到達lastTimestamp的下一個毫秒時間戳 nowTimestamp=s.tilNextMills() } }else{ s.sequence=0 } s.lastTimestamp=nowTimestamp return (nowTimestamp-s.twepoch)<<s.timestampShift| //時間戳差值部分 s.datacenterId<<s.datacenterIdShift| //數據中心部分 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秒到此時時刻的總秒數) 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位均設為1,其余設為0 mySnow.maxDatacenterId=-1^(-1<<mySnow.datacenterIdBits) //64位末尾datacenterIdBits位均設為1,其余設為0 mySnow.maxSequence=-1^(-1<<mySnow.sequenceBits) //64位末尾sequenceBits位均設為1,其余設為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(){ //模擬實驗是生成并發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) //生成并發的數為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數在400W以上)
優點:
毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增不依賴數據庫等第三方系統,以服務的方式部署,穩定性更高,生成的ID性能也是非常高的可以根據自身業務特性分配bit位,非常靈活
缺陷:
1. 依賴機器時鐘,如果**機器時鐘回撥**,會導致重復ID生成.
2. 在單機上是遞增的,但是由于設計到分布式環境下,每臺機器上的時鐘不可能完全同步,有時候會出現不是全局遞增的情況.
如何解決單機系統中時鐘回撥問題:
? 可以分為兩種情況:
1. 如果**時間回撥時間較短,比如配置5ms以內**,那么可以直接等候一定的時間,讓機器時間追上來
2. 如果**時間回撥時間較長**,我們不能接收這么長的阻塞等候,那么就有兩個策略,直接拒絕,拋出異常;或者通過RD時鐘回滾
布式環境下,每臺機器上的時鐘不可能完全同步,有時候會出現不是全局遞增的情況.
如何解決單機系統中時鐘回撥問題:
? 可以分為兩種情況:
1. 如果**時間回撥時間較短,比如配置5ms以內**,那么可以直接等候一定的時間,讓機器時間追上來
2. 如果**時間回撥時間較長**,我們不能接收這么長的阻塞等候,那么就有兩個策略,直接拒絕,拋出異常;或者通過RD時鐘回滾
參考博客高并發情況下,雪花ID一秒400W個,以及分布式ID算法(詳析)
原文鏈接:https://blog.csdn.net/Blockchain210/article/details/124217374
相關推薦
- 2022-06-18 Android自定義彈框Dialog效果_Android
- 2022-07-07 python如何實現數組元素兩兩相加_python
- 2022-09-16 Python利用LyScript插件實現批量打開關閉進程_python
- 2022-02-11 g2繪制點圖,鼠標移入點上出現tooltip,對應的label消失
- 2022-08-15 gin框架中使用websocket發送消息及群聊
- 2022-07-23 Python代碼實現雙鏈表_python
- 2022-03-16 Linux環境下安裝nginx教程_nginx
- 2022-05-27 C++超詳細分析單鏈表的實現與常見接口_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支