分布式唯一ID(一)--常見的分布式唯一ID生成方案


一、背景:

當需要給數據添加唯一標識符,就需要分布式唯一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,是最推薦的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM