一篇帶你了解什么是分布式ID
一 .什么是分布式ID
在分布式系統(tǒng)中,經(jīng)常需要一些全局唯一的ID對數(shù)據(jù)、消息、http請求等進行唯一標識。那么這個全局唯一ID就叫分布式ID
二 .為什么需要分布式ID
1.如果id我們使用的是數(shù)據(jù)庫的自增長類型,在分布式系統(tǒng)中需要分庫和分表時,會有兩個相同的表,有可能產(chǎn)生主鍵沖突。
2.電商訂單號,采用自增方式,是最簡單的生成規(guī)則。但是!這種與流水號相同的訂單號很容易就被競爭對手看出你公司真實的運營信息。
三 .分布式ID需要滿足哪些條件
全局唯一:必須保證ID是全局性唯一的
高性能:高可用低延時,ID生成響應要快,否則會成為業(yè)務瓶頸
高可用:100%的可用性是騙人的,但是也要無限接近于100%的可用性
好接入:要秉著拿來即用的設(shè)計原則,在系統(tǒng)設(shè)計和實現(xiàn)上要盡可能的簡單
趨勢遞增:最好趨勢遞增,這個要求就得看具體業(yè)務場景了,一般不嚴格要求
安全性:如果ID連續(xù)生成,勢必會泄露業(yè)務信息,甚至可能被猜出,所以需要無規(guī)則不規(guī)則。
易讀性:不要太長,想象一下用戶在售后的時候本身就處在一個焦躁的心情中,再讓他報/輸一個很長的訂單號,很容易造成錯誤率高,這個時候只能煩上加煩。如果訂單號實在太長,還有一種辦法:斷句。
可擴展性:淘寶的訂單號在最初的時候也還是12位、14位,現(xiàn)在已經(jīng)變成16位了,隨著一個網(wǎng)站的交易量逐年上升,訂單號不可避免的會變長。
四 .分布式ID生成方式
- UUID
- 數(shù)據(jù)庫自增ID
- 數(shù)據(jù)庫多主模式
- 號段模式
- Redis
- 雪花算法(SnowFlake)
- 美團(Leaf)
注:主流生成ID方案都是基于數(shù)據(jù)庫號段模式和雪花算法
五 .優(yōu)缺點
1. 基于UUID
優(yōu)點:
- 生成足夠簡單,本地生成無網(wǎng)絡(luò)消耗,具有唯一性
缺點:
- 無序的字符串,不具備趨勢自增特性
- 沒有具體的業(yè)務含義
- 長度過長16字節(jié)128位,36位長度的字符串,存儲以及查詢對MySQL的性能消耗較大,MySQL官方明確建議主鍵要盡量越短越好,作為數(shù)據(jù)庫主鍵UUID的無序性會導致數(shù)據(jù)位置頻繁變動,嚴重影響性能。
2. 基于數(shù)據(jù)庫自增ID
當我們需要一個ID的時候,向表中插入一條記錄返回主鍵ID,但這種方式有一個比較致命的缺點,訪問量激增時MySQL本身就是系統(tǒng)的瓶頸,用它來實現(xiàn)分布式服務風險比較大,不推薦!
優(yōu)點:
實現(xiàn)簡單,ID單調(diào)自增,數(shù)值類型查詢速度快
缺點:
DB單點存在宕機風險,無法扛住高并發(fā)場景
3. 基于數(shù)據(jù)庫集群模式
前邊說了單點數(shù)據(jù)庫方式不可取,那對上邊的方式做一些高可用優(yōu)化,換成主從模式集群。害怕一個主節(jié)點掛掉沒法用,那就做雙主模式集群,也就是兩個Mysql實例都能單獨的生產(chǎn)自增ID。那這樣還會有個問題,兩個MySQL實例的自增ID都從1開始,會生成重復的ID怎么辦?
解決方案:設(shè)置起始值和自增步長
MySQL_1 配置:
- set @@auto_increment_offset = 1; -- 起始值
- set @@auto_increment_increment = 2; -- 步長
MySQL_2 配置:
- set @@auto_increment_offset = 2; -- 起始值
- set @@auto_increment_increment = 2; -- 步長
這樣兩個MySQL實例的自增ID分別就是:
- 1、3、5、7、9
- 2、4、6、8、10
那如果集群后的性能還是扛不住高并發(fā)咋辦?就要進行MySQL擴容增加節(jié)點,這是一個比較麻煩的事。
增加第三臺MySQL實例需要人工修改一、二兩臺MySQL實例的起始值和步長,把第三臺機器的ID起始生成位置設(shè)定在比現(xiàn)有最大自增ID的位置遠一些,但必須在一、二兩臺MySQL實例ID還沒有增長到第三臺MySQL實例的起始ID值的時候,否則自增ID就要出現(xiàn)重復了,必要時可能還需要停機修改。
優(yōu)點:
- 解決DB單點問題
缺點:
- 不利于后續(xù)擴容,而且實際上單個數(shù)據(jù)庫自身壓力還是大,依舊無法滿足高并發(fā)場景。
4. 基于數(shù)據(jù)庫的號段模式
號段模式是當下分布式ID生成器的主流實現(xiàn)方式之一,號段模式可以理解為從數(shù)據(jù)庫批量的獲取自增ID,每次從數(shù)據(jù)庫取出一個號段范圍,例如 (1,1000] 代表1000個ID,具體的業(yè)務服務將本號段,生成1~1000的自增ID并加載到內(nèi)存。
由于多業(yè)務端可能同時操作,所以采用版本號version樂觀鎖方式更新,這種分布式ID生成方式不強依賴于數(shù)據(jù)庫,不會頻繁的訪問數(shù)據(jù)庫,對數(shù)據(jù)庫的壓力小很多。
5. 基于Redis模式
利用redis的incr命令實現(xiàn)ID的原子性自增。
- 127.0.0.1:6379> set seq_id 1 // 初始化自增ID為1
- OK
- 127.0.0.1:6379> incr seq_id // 增加1,并返回遞增后的數(shù)值
- (integer) 2
用redis實現(xiàn)需要注意一點,要考慮到redis持久化的問題。redis有兩種持久化方式RDB和AOF
- RDB會定時打一個快照進行持久化,假如連續(xù)自增但redis沒及時持久化,而這會Redis掛掉了,重啟Redis后會出現(xiàn)ID重復的情況。
- AOF會對每條寫命令進行持久化,即使Redis掛掉了也不會出現(xiàn)ID重復的情況,但由于incr命令的特殊性,會導致Redis重啟恢復的數(shù)據(jù)時間過長。
6. 基于雪花算法(Snowflake)模式
雪花算法(Snowflake)是twitter公司內(nèi)部分布式項目采用的ID生成算法
Snowflake生成的是Long類型的ID,一個Long類型占8個字節(jié),每個字節(jié)占8比特,也就是說一個Long類型占64個比特。Snowflake ID組成結(jié)構(gòu):正數(shù)位(占1比特)+ 時間戳(占41比特)+ 機器ID(占5比特)+ 數(shù)據(jù)中心(占5比特)+ 自增值(占12比特),總共64比特組成的一個Long類型。
- 第一個bit位(1bit):Java中l(wèi)ong的最高位是符號位代表正負,正數(shù)是0,負數(shù)是1,一般生成ID都為正數(shù),所以默認為0。
- 時間戳部分(41bit):毫秒級的時間,不建議存當前時間戳,而是用(當前時間戳 - 固定開始時間戳)的差值,可以使產(chǎn)生的ID從更小的值開始;41位的時間戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
- 工作機器id(10bit):也被叫做workId,這個可以靈活配置,機房或者機器號組合都可以。
- 序列號部分(12bit),自增值支持同一毫秒內(nèi)同一個節(jié)點可以生成4096個ID
根據(jù)這個算法的邏輯,只需要將這個算法用Java語言實現(xiàn)出來,封裝為一個工具方法,那么各個業(yè)務應用可以直接使用該工具方法來獲取分布式ID,只需保證每個業(yè)務應用有自己的工作機器id即可,而不需要單獨去搭建一個獲取分布式ID的應用。
雪花算法目前存在時間回撥問題,而且不同的機器也無法完全保證時間一樣,所以可能會出現(xiàn)重復問題。
7. 美團(Leaf)
Leaf由美團開發(fā),支持號段模式和snowflake算法模式,可以切換使用。
