目錄
一、背景:
當需要給數據添加唯一標識符,就需要分布式唯一ID生成器。
如果MySQL是單庫單表,直接使用數據庫的自增主鍵就可以了。
如果是分庫分表,肯定無法使用自增主鍵來完成。
二、常見的分布式唯一ID生成方案:
1、數據庫自增主鍵:
通過一個表來生成全局唯一ID,插入一條數據,返回一個全局唯一的ID,保證全局唯一。
優點:
- 實現簡單,很容易落地,專門搞個對應的庫和表就行了。
缺點:
- 單庫單表,抗不住太高的並發,如果並發達到幾千,機器可能就有掛的風險。
- 單庫有高可用的問題。
- 隨着不斷插入表數據會越來越多,需要定期清理。
適用場景:
很少直接在生產環境直接使用這個方案,通過flickr實現這個更好一點。
2、UUID:
Java自帶的UUID api就可以生成一個唯一id。
UUID.randomUUID().toString()
UUID.randomUUID().toString().replaceAll("-", "")
76dfa90b-8e45-4ec7-838a-3aa24de79482
3fd88264b6664d9ca993236bcc7ea1a6
優點:
- 本地生成,沒有並發壓力。
缺點:
- 字段太長了;
- 作為主鍵不太靠譜,因為不是有序的,會出現數據庫頻繁頁分裂問題!
適用場景:
- 除數據庫主鍵之外的其他唯一鍵場景,如很多業務編碼,都是適用的。
3、Twitter開源的SnowFlake方案:
核心思想:
- 64個bit位,最高位1個bit是0,41位放時間戳(單位毫秒,最多使用69年);
- 10位放機器標識(最多可以部署在1024台機器上);
- 12位放序號(每毫秒,每台機器,可以順序生成4096個ID);
- 通過時間戳 + 機器id + 序號 -> long類型的唯一id。
SnowFlake程序分布式部署在多台機器上,每台機器每毫秒最多4096個ID,基於內存生成,性能高的一批,不用擔心並發問題。
優點:
- 高並發,高可用,集群可伸縮,最多擴展1024台機器。
缺點:
- 目前的開源算法還需要考慮時鍾回撥等問題,如果想要解決,還要重新開發。
適用場景:
- 中大型公司,對於高並發生成唯一ID場景,基於snowflake算法自研。
- 加入時鍾回撥、多機房等解決方案。
4、Redis自增機制:
核心思想:
- Redis能夠實現有序自增incrby;
- 例如5台機器集群部署,那么每台機器的初始值依次為1、2、3、4、5,每台機器的自增步長是5。
- 第1台機器就是1、6、11、16、21,
- 第2台機器就是2、7、12、17、22,
- 以此類推。。。
- 直到第5台機器就是5、10、15、20、25。
優點:
- 公司幾乎都有Redis集群,可以直接用,或者申請獨立的集群。
- 高並發,高可用,集群化,全局唯一。
缺點:
- 客戶端需要自己開發封裝。
- Redis機器數量是否要支持配置,因為萬一需要加機器呢,支持動態感知嗎?
- 擴容之后,步長就變了,之前的數據是否需要處理。
適用場景:
- 一般不用redis集群玩自增主鍵生成。
- 對未來的並發是可預期的。
- Redis主從同步是異步的,如果故障轉移,是不是有可能出現重復ID。
5、時間戳 + 業務id:
1、業務背景:
例如打車業務,需要生成訂單ID。
2、實現:
- 打車:時間戳 + 起點編號 + 車牌號,肯定是能保證唯一的。
- 電商:可以用時間戳 + 用戶ID + 渠道 + 其他業務id,也是可以保證唯一的。
3、優點:
- 實現簡單,沒額外成本,沒並發之類的擴容問題。
4、缺點:
- 不是所有的業務場景都能這樣用,例如現在用戶模塊需要做分庫分表。
5、適用場景:
- 如果業務上能使用這個方案,建議使用。
6、flickr(雅虎旗下的圖片分享平台)的數據庫唯一id生成方案:
1、創建數據庫表:
CREATE TABLE `id_generator` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`stub` char(1) NOT NULL default '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM;
1、優化:
每一台機器要申請一個唯一id,用自己機器的ip地址去replace into,那么自己的機器id不停自增,通過下面語句查詢:
REPLACE INTO uid_sequence (stub) VALUES ('a')
SELECT LAST_INSERT_ID();
如果是不同的業務:不同的業務都會有自己的一條數據:
1 order
5 account
2、優點:
- 用replace into替代了insert into,避免表數據量過大。
3、缺點:
- 用這個方案生成唯一id,低並發場景下可以用於生產。
4、建議:
- 而且一般會部署數據庫高可用方案,MySQL雙機高可用方案,兩個庫設置不同的起始位置和步長,分別是1、3、5,以及2、4、6。
TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1
TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2
7、基於flickr方案的高並發優化:
1、背景:
flickr方案的核心問題在於並發瓶頸,所以可以把ID優化為號段。
2、封裝客戶端:
- 每台機器都引入封裝的客戶端,只要一旦服務啟動,客戶端就直接有一個線程采用flickr方案獲取一個id。
- 當服務啟動,通過flickr方案的replace into拿到id為1。
- 每個號段是10000個id號,id就是[10000, 20000)。
volatile AtomicLong idGenerator = new AtomicLong(10000)
volatile long maxId = 20000
3、獲取ID:
- 通過封裝的客戶端,IdGenerator.next(),每次拿一個id,就是AtomicLong.incrementAndGet(),直接原子遞增。
- 如果拿到了號段里最大id,此時需要進行阻塞。
- 然后重新到數據庫獲取ID。
4、特點:
高可用 -> 兩台數據庫(不同起始offset,相同步長)+ 故障自動轉移
不需要考慮表數據量
支持多種業務
高並發 + 高性能 -> 不需要伸縮和擴容
號段自動更新 + 號段本地磁盤持久化
5、缺點:
- 每次重啟服務,就會浪費一個號段里還沒自增到的ID,重啟后又是新的號段。
- 如果要優化,可以在spring銷毀事件里,不允許獲取id了,接着把AtomicLong的值持久化到本地磁盤,下次服務重啟后直接從本地磁盤里讀取。
6、總結:
- 優化后的方案可以直接用到生產,我司也是這個方案,只是做了改動。
- 相對沒有snowflake生產級方案具備普適性,SnowFlake不涉及號段問題,不依賴數據庫,就是peer-to-peer的集群架構,隨時可以擴容。
- 時間戳+業務id,是最推薦的。