分布式系統(tǒng)里用戶ID生成有什么好的方法和規(guī)則能滿足“少數(shù)、盡量短、不能直接看出規(guī)則”這幾個條件?
1、基于UUID
在Java的世界里,想要得到一個具有少數(shù)性的ID,首先被想到可能就是UUID,畢竟它有著全球少數(shù)的特性。那么UUID可以做分布式ID嗎?答案是可以的,但是并不是十分推薦。
public static void main(String[] args) { String uuid = UUID.randomUUID().toString().replaceAll("-",""); System.out.println(uuid); }
UUID的生成簡單到只有一行代碼,輸出結(jié)果?c2b8c2b9e46c47e3b30dca3b0d447718,但UUID卻并不適用于實際的業(yè)務需求。像用作訂單號UUID這樣的字符串沒有絲毫的意義,看不出和訂單相關的有用信息;而對于數(shù)據(jù)庫來說用作業(yè)務主鍵ID,它不僅是太長還是字符串,存儲性能差查詢也很耗時,所以不推薦用作分布式ID。
優(yōu)點:生成足夠簡單,本地生成無網(wǎng)絡消耗,具有少數(shù)性。
缺點:
無序的字符串,不具備趨勢自增特性;沒有具體的業(yè)務含義;長度過長16 字節(jié)128位,36位長度的字符串,存儲以及查詢對MySQL的性能消耗較大,MySQL官方明確建議主鍵要盡量越短越好,作為數(shù)據(jù)庫主鍵?UUID?的無序性會導致數(shù)據(jù)位置頻繁變動,嚴重影響性能。2、基于數(shù)據(jù)庫自增ID
基于數(shù)據(jù)庫的auto_increment自增ID完全可以充當分布式ID,具體實現(xiàn):需要一個單獨的MySQL實例用來生成ID,建表結(jié)構(gòu)如下:
當我們需要一個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怎么辦?
解決方案:設置起始值和自增步長。
MySQL_1 配置:
MySQL_2 配置:
這樣兩個MySQL實例的自增ID分別就是:
1、3、5、7、92、4、6、8、10
那如果集群后的性能還是扛不住高并發(fā)咋辦?就要進行MySQL擴容增加節(jié)點,這是一個比較麻煩的事。
從上圖可以看出,水平擴展的數(shù)據(jù)庫集群,有利于解決數(shù)據(jù)庫單點壓力的問題,同時為了ID生成特性,將自增步長按照機器數(shù)量來設置。增加第三臺MySQL實例需要人工修改一、二兩臺MySQL實例的起始值和步長,把第三臺機器的ID起始生成位置設定在比現(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)存。表結(jié)構(gòu)如下:
CREATE TABLE id_generator ( id int(10) NOT NULL, max_id bigint(20) NOT NULL COMMENT '當前最大id', step int(20) NOT NULL COMMENT '號段的布長', biz_type int(20) NOT NULL COMMENT '業(yè)務類型', version int(20) NOT NULL COMMENT '版本號', PRIMARY KEY (id))
biz_type?:代表不同業(yè)務類型max_id?:當前最大的可用idstep?:代表號段的長度version?:是一個樂觀鎖,每次都更新version,保證并發(fā)時數(shù)據(jù)的正確性等這批號段ID用完,再次向數(shù)據(jù)庫申請新號段,對max_id字段做一次update操作,update max_id= max_id + step,update成功則說明新號段獲取成功,新的號段范圍是(max_id ,max_id +step]。
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX
由于多業(yè)務端可能同時操作,所以采用版本號version樂觀鎖方式更新,這種分布式ID生成方式不強依賴于數(shù)據(jù)庫,不會頻繁的訪問數(shù)據(jù)庫,對數(shù)據(jù)庫的壓力小很多。
5、基于Redis模式
Redis也同樣可以實現(xiàn),原理就是利用redis的?incr命令實現(xiàn)ID的原子性自增:
用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生成算法,開源后廣受國內(nèi)大廠的好評,在該算法影響下各大公司相繼開發(fā)出各具特色的分布式生成器。
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的應用。Java版本的****Snowflake算法實現(xiàn):
public class SnowFlakeShortUrl { private final static long START_TIMESTAMP = 1480166465631L; private final static long SEQUENCE_BIT = 12; //序列號占用的位數(shù) private final static long MACHINE_BIT = 5; //機器標識占用的位數(shù) private final static long DATA_CENTER_BIT = 5; //數(shù)據(jù)中心占用的位數(shù) private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT); private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT; private long dataCenterId; //數(shù)據(jù)中心 private long machineId; //機器標識 private long sequence = 0L; //序列號 private long lastTimeStamp = -1L; //上一次時間戳 private long getNextMill() { long mill = getNewTimeStamp(); while (mill <= lastTimeStamp) { mill = getNewTimeStamp(); } return mill;} private long getNewTimeStamp() { return System.currentTimeMillis(); } public SnowFlakeShortUrl(long dataCenterId, long machineId) { if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) { throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!"); } this.dataCenterId = dataCenterId; this.machineId = machineId; } public synchronized long nextId() { long currTimeStamp = getNewTimeStamp(); if (currTimeStamp < lastTimeStamp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currTimeStamp == lastTimeStamp) { //相同毫秒內(nèi),序列號自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列數(shù)已經(jīng)達到最大 if (sequence == 0L) { currTimeStamp = getNextMill(); } } else { //不同毫秒內(nèi),序列號置為0 sequence = 0L; } lastTimeStamp = currTimeStamp; return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //時間戳部分 | dataCenterId << DATA_CENTER_LEFT //數(shù)據(jù)中心部分 | machineId << MACHINE_LEFT //機器標識部分 | sequence; //序列號部分 } public static void main(String[] args) { SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3); for (int i = 0; i < (1 << 4); i++) { System.out.println(snowFlake.nextId()); } }}
7、百度(uid-generator)
uid-generator是由百度技術(shù)部開發(fā),項目GitHub地址為https://github.com/baidu/uid-generator。uid-generator是基于Snowflake算法實現(xiàn)的,與原始的snowflake算法不同在于,uid-generator支持自定義時間戳、工作機器ID和?序列號?等各部分的位數(shù),而且uid-generator中采用用戶自定義workId的生成策略。
uid-generator需要與數(shù)據(jù)庫配合使用,需要新增一個WORKER_NODE表。當應用啟動時會向數(shù)據(jù)庫表中去插入一條數(shù)據(jù),插入成功后返回的自增ID就是該機器的workId數(shù)據(jù)由host,port組成。對于****uid-generator?ID組成結(jié)構(gòu):workId,占用了22個bit位,時間占用了28個bit位,序列化占用了13個bit位,需要注意的是,和原始的snowflake不太一樣,時間的單位是秒,而不是毫秒,workId也不一樣,而且同一應用每次重啟就會消費一個workId。
延伸閱讀1:分布式ID的特點
少數(shù)性:生成的ID全局少數(shù),在特定范圍內(nèi)沖突概率極小。有序性:生成的ID按某種規(guī)則有序,便于數(shù)據(jù)庫插入及排序。可用性:可保證高并發(fā)下的可用性,確保任何時候都能正確的生成ID。自主性:分布式環(huán)境下不依賴中心認證即可自行生成ID。安全性:不暴露系統(tǒng)和業(yè)務的信息,如:訂單數(shù),用戶數(shù)等。
相關推薦HOT
更多>>
在 iPad 上運行 Windows 是什么體驗?
一、在 iPad 上運行 Windows 是什么體驗目前市面上有一些能夠在 iPad 上運行 Windows 的應用程序,例如 Parallels Access、Splash較好、VMware ...詳情>>
2023-10-14 19:14:27
vector, list, map等容器使用場合是什么?
一、vector, list, map等容器使用場合vector適用于對象簡單,變化較小,并且頻繁隨機訪問的場景。list適用經(jīng)常進行插入和刪除并且不經(jīng)常隨機訪...詳情>>
2023-10-14 14:59:11
分庫分表的數(shù)據(jù)庫和分布式數(shù)據(jù)庫有什么區(qū)別?
一、分庫分表的數(shù)據(jù)庫和分布式數(shù)據(jù)庫有什么區(qū)別分庫分表的數(shù)據(jù)庫:沒有這種數(shù)據(jù)庫,所謂分庫分表,這是開發(fā)應用的程序員通過自己的代碼、或者底...詳情>>
2023-10-14 13:59:18
APP定制開發(fā)的難點有哪些?
一、APP定制開發(fā)的難點1、多平臺適配不同的移動平臺(如iOS和Android)具有不同的操作系統(tǒng)、開發(fā)語言和開發(fā)工具。在進行APP定制開發(fā)時,需要適...詳情>>
2023-10-14 12:57:35熱門推薦
技術(shù)干貨







快速通道 更多>>
-
課程介紹
點擊獲取大綱 -
就業(yè)前景
查看就業(yè)薪資 -
學習費用
了解課程價格 -
優(yōu)惠活動
領取優(yōu)惠券 -
學習資源
領3000G教程 -
師資團隊
了解師資團隊 -
實戰(zhàn)項目
獲取項目源碼 -
開班地區(qū)
查看來校路線