天天看點

分布式唯一ID分布式唯一ID

  • 分布式唯一ID
    • 1. 背景
    • 2. 概念
      • 2.1 什麼是分布式唯一ID
      • 2.2 分布式唯一ID的特性
      • 2.3 單調遞增和趨勢遞增
      • 2.4 設計分布式ID系統需注意的點
      • 2.5 分布式ID系統的難點
    • 3. 分布式ID生成方案
      • 3.1 UUID
        • 3.1.1 UUID概念
        • 3.1.2 UUID優缺點
        • 3.1.3 Java生成UUID
      • 3.2 資料庫自增
        • 3.2.1 概念
        • 3.2.2 優缺點
        • 3.2.3 MySQL設定自增的方式
      • 3.3 資料庫叢集模式
        • 3.3.1 原理
        • 3.3.2 優缺點
        • 3.3.3 MySQL設定起始值和步長的方法
      • 3.4 資料庫号段模式
        • 3.4.1 原理
        • 3.4.2 優缺點
        • 3.4.3 實作
          • 3.4.3.1 樂觀鎖實作
          • 3.4.3.2 悲觀鎖實作
      • 3.5 Redis/Zookeeper
        • 3.5.1 Redis方式原理
          • 3.5.1.1 強依賴redis方案
          • 3.5.1.2 弱依賴redis方案
        • 3.5.2 優缺點
        • 3.5.3 實作(redis自增模式的實作)
      • 3.6 雪花算法
        • 3.6.1 原理
        • 3.6.2 優缺點
        • 3.6.3 實作
      • 3.7 美團Leaf算法
      • 3.8 百度uid-generator
      • 3.9 TinyId
    • 4 分布式ID生成方案總結
    • 5 參考資料

分布式唯一ID

1. 背景

我們在設計和實作網際網路系統時,往往需要使用到唯一ID。唯一ID辨別唯一的一條業務請求,如在電商系統中,ID表示系統中唯一的一個訂單,支付系統中表示唯一的一條交易請求。在單機應用中,唯一ID的生成是比較簡單的,我們隻需保證ID在單機上面是唯一的即可;但目前網際網路應用多為微服務應用,同時随着業務的逐漸增長,必須對業務進行分庫分表,而且業務應用往往是多執行個體部署的,這就要求ID在多個微服務應用和多個應用執行個體之間是唯一。目前業界有很多成熟的唯一ID生成方案,下面我們來看下這些分布式唯一的ID生成方案。

2. 概念

2.1 什麼是分布式唯一ID

分布式唯一ID, 應該拆開來解釋:

  • 唯一ID: 顧名思義, 這個就不用多解釋了
  • 分布式: 分布式對應的是單機應用, 單機應用中唯一ID即是唯一ID,而分庫分表應用中多執行個體之間, 各微服務應用之間全局唯一的ID即是分布式式唯一ID

2.2 分布式唯一ID的特性

  • 全局唯一:唯一ID,顧名思義;
  • 高性能:ID生成的性能要高,否則會影響業務系統的性能;
  • 高可用:ID生成服務要是高可用的,否則一旦出現不可用則會影響業務系統的可用性;
  • 遞增;盡量保證是遞增的,因為業務系統可能需要根據ID進行排序,分為單調遞增和趨勢遞增;
  • 長度:長度盡可能的短;
  • 侵入性小:盡量做到對業務系統無侵入。

此外,根據業務的不同,還可能要存在其他特性:

  • 時間:有的ID中需要包含時間戳;
  • 業務含義:這個要看具體的業務需求。

2.3 單調遞增和趨勢遞增

  • 單調遞增:ID是嚴格遞增的,後面的請求ID一定比之前的請求的ID大;
  • 趨勢遞增:ID的整體趨勢是遞增的,不過存在後面的請求的ID比之前的請求ID小的情況。比如這樣的ID生成方案:考慮這樣的一個場景,2個執行個體的業務應用,執行個體1緩存0-1000的ID在程序中,執行個體2緩存1001-2000的ID在程序中,請求1先請求執行個體2(随機負載均衡),ID為1001,然後1分鐘後請求2請求執行個體1,ID為0,即出現後面的請求的ID比之前的請求的ID小的情況,但是ID配置設定的整體趨勢是遞增的。

2.4 設計分布式ID系統需注意的點

  • 時鐘回退:如果ID中包含時間戳資訊,需考慮時鐘回退的場景,如果出現時鐘回退則可能出現重複ID;
  • 多線程問題:ID生成往往是在多線程的環境下的,是以需要注意多線程問題;
  • 資料庫性能:有些ID生成算法依賴資料庫中的記錄,需要考慮資料庫的性能問題以及多線程引發的資料庫鎖問題

2.5 分布式ID系統的難點

  • ID長度盡可能的短:我們的ID大多數是需要在資料庫中存儲的,越長的ID占用的空間越多;如果使用該ID作為MySQL的主鍵,主鍵也是要求盡可能的短的,是以ID長度要盡可能的短。随之而來的問題是,越短的ID,其中包含的資訊越少(資訊熵越小),沖突的可能性就越大;
  • 多線程問題:多執行個體之間,同一執行個體之間的線程都會搶ID,是以必須注意多線程問題,解決方法大部分是加鎖,如果線程之間競争嚴重,則嚴重形象性能。

3. 分布式ID生成方案

3.1 UUID

3.1.1 UUID概念

UUID(universally unique identifier)是一串随機的32位長度的資料,每一位是16進制表示,是以總計能夠表示2^128的數字

據統計若每納秒産生1百萬個 UUID,要花100億年才會将所有 UUID 用完

UUID的生成用到了以太網卡位址、納秒級時間、晶片ID碼和許多可能的數字

UUID為16進制的32位元組長度,中間以

-

相連,形式為8-4-4-4-12,是以說長度也可以是36,不過使用時一般不包含

-

,UUID的形式如下:

ef56e7fd-225b-44b8-b96d-4591bde0945b
********-****-M***-N***-************
           
上面的以數字

M

開頭的四位表示UUID 版本,目前UUID的規範有5個版本,M可選值為1, 2, 3, 4, 5 ;各個版本的具體介紹如下所示:
  • version 1:0001。基于時間和 MAC 位址。由于使用了 MAC 位址,是以能夠確定唯一性,但是同時也暴露了 MAC 位址,私密性不夠好。
  • version 2:0010。DCE 安全的 UUID。該版本在規範中并沒有仔細說明,是以并沒有具體的實作。
  • version 3:0011。基于名字空間 (MD5)。使用者指定一個名字空間和一個字元串,通過 MD5 散列,生成 UUID。字元串本身需要是唯一的。
  • version 4:0100。基于随機數。雖然是基于随機數,但是重複的可能性可以忽略不計,是以該版本也是被經常使用的版本。
  • version 5:0101。基于名字空間 (SHA1)。跟 Version 3 類似,但是散列函數程式設計了 SHA1。
上面以數字

N

開頭的四個位表示 UUID 變體( variant ),變體是為了能相容過去的 UUID,以及應對未來的變化,目前已知的變體有如下幾種,因為目前正在使用的 UUID 都是 variant1,是以取值隻能是 8,9,a,b 中的一個(分别對應1000,1001,1010,1011)。
  • variant 0:0xxx。為了向後相容預留。
  • variant 1:10xx。目前正在使用的。
  • variant 2:11xx。為早期微軟 GUID 預留。
  • variant 3:111x。為将來擴充預留。目前暫未使用。

3.1.2 UUID優缺點

優點

  • 無需網絡,單機即可生成
  • 速度快
  • 生成簡單,有内置的函數庫可直接實作
  • 沒有業務含義,随機性較好,保密性強(業務層保密,有些版本的UUID可洩露IP或者MAC位址)
  • 機器生成,可保證唯一,講道理不會重複,但是也存在重複的機率(很低)

缺點

  • 沒有業務含義,如果ID中需要業務含義則不适用UUID
  • 太長了,32位長度
  • 無序,可讀性差
  • 有些版本的UUID可洩露IP或者MAC位址

3.1.3 Java生成UUID

Java内置了

java.util.UUID

類,其中内置了四種版本的UUID生成政策,包括基于時間和MAC位址的、DCE 安全的UUID、基于名字空間(MD5)和基于随機數的:

There are four different basic types of UUIDs: time-based, DCE

security, name-based, and randomly generated UUIDs. These types have a

version value of 1, 2, 3 and 4, respectively.

比較常用的即是基于随機數的UUID生成,下面是Java的使用方法

3.2 資料庫自增

3.2.1 概念

使用資料庫的id自增政策,比如Mysql的auto_increment

3.2.2 優缺點

優點

  • ID單調遞增,對業務友好,利于分頁和排序;
  • 簡單,代碼中無需設定,隻需要在建表時設定主機遞增即可;

缺點

  • 強依賴資料庫, 性能存在瓶頸
  • 單庫時使用友善,分庫分表後就難受了
  • 難以擴充
  • 隻能主庫生成,單點故障了就gg了
  • 資料庫文法不同,遷移時需要考慮
  • 容易洩密,比如訂單号是遞增的,那麼可以猜到别人的訂單号

3.2.3 MySQL設定自增的方式

隻需設定主鍵自增即可,如下面的建表語句

CREATE TABLE `user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(64) NOT NULL COMMENT '名字',
  `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '昵稱',
  `create_time` datetime NOT NULL COMMENT '建立時間',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
           

3.3 資料庫叢集模式

3.3.1 原理

資料庫自增模式生成的唯一ID,當遇到分庫分表場景時, ID則無法解決ID重複的問題,而且ID依賴于資料庫,無法滿足業務系統高并發以及高可用的需求.一種解決方案是使用資料庫多主模式,也即資料庫叢集中每個執行個體都可以生成ID, 并且設定自增ID的起始值和步長

其原理圖如下:

分布式唯一ID分布式唯一ID

3.3.2 優缺點

優點

  • 解決資料庫自增模式下的單點故障問題以及性能問題
  • 設定同樣簡單,隻比資料庫自增模式複雜一些
  • ID趨勢自增,在同一資料庫中是嚴格遞增的

缺點

  • 不利于後續擴容: 當我們在開始時設定步長為4時,這樣每台資料庫執行個體生成的ID如上圖所示(圖中下面方框中),但是随着業務增長,如果需要水準擴充資料庫,即再增加一個資料庫執行個體,這種情況下需将第5台執行個體的步長設定為5,第5台執行個體的起始位置要大于前4台執行個體中ID的最大值(預留部分,防止此段時間前4個執行個體中ID超過該值), 并且将前4台執行個體的步長修改為5. 而且還存在停機修改的可能性; 一種解決方案是初始時預留足夠的步長,比如初始時有4台執行個體,但是設定步長可以為8,後續可水準擴充(當然缺點是單個執行個體ID耗盡速度*2)
  • 強依賴資料庫,同樣存在性能問題. 如果是水準切分,如根據使用者ID分庫分表,那麼某個分庫挂了同樣會影響可用性

3.3.3 MySQL設定起始值和步長的方法

執行個體1:

set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 6;  -- 步長

執行個體2:

set @@auto_increment_offset = 2;     -- 起始值
set @@auto_increment_increment = 6;  -- 步長

執行個體3:

set @@auto_increment_offset = 3;     -- 起始值
set @@auto_increment_increment = 6;  -- 步長

執行個體4:

set @@auto_increment_offset = 4;     -- 起始值
set @@auto_increment_increment = 6;  -- 步長
           

3.4 資料庫号段模式

3.4.1 原理

資料和本地緩存相結合的方式, 每個執行個體首先從資料庫中取出一個ID的生成範圍,然後需要使用時采用線程安全的方式從本地緩存的ID範圍中取出即可,等執行個體内緩存的ID耗盡時再從資料庫中取出一個ID生成範圍

分布式唯一ID分布式唯一ID

3.4.2 優缺點

優點

  • 弱依賴資料庫,每次從資料庫中取出一個ID範圍,提高了可用性和ID生成的性能. 即使生成ID的資料庫挂了,執行個體中緩存的ID也能撐一段,而這段時間已經足夠資料庫恢複了(ID資料庫和業務資料庫是同一個資料庫的話,就沒用了)
  • ID趨勢遞增

缺點

  • 執行個體挂了, 則緩存的号段就丢失了
  • 應用自身需保證線程安全, 可以采用AtomicLong的方式來保證
  • 如果在号段耗盡後再去資料庫取新的号段, 如果并發較高, 那麼取号段的操作也會出現競争嚴重的場景, 導緻資料庫壓力較大, 一種解決方案是采用雙buffer的方案, 即當第一個号段buffer1耗盡10%的時候, 開啟一個線程取資料庫中取号段緩存到另外一個buffer2中, 當第一個buffer1耗盡後能夠立即切換到buffer2中;
  • 不夠随機, 訂單場景不适用, 如果每天中午12點下單, 訂單号相減能夠大緻推斷出一天的訂單量.

3.4.3 實作

号段模式可以有悲觀鎖和樂觀鎖兩種實作, 這裡我覺得兩種方式都可以, 因為該唯一ID的生成方式就說明不會頻繁的讀寫資料庫,是以資料庫的鎖競争不會很大, 樂觀鎖和悲觀鎖方式都可以應該都可以滿足需求, 不過使用時還是需要按照具體的業務來設計.

3.4.3.1 樂觀鎖實作
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 '業務類型',
  version int(20) NOT NULL COMMENT '版本号',
  PRIMARY KEY (`id`)
) 

           

執行個體取ID的步驟:

  1. 從資料庫中擷取目前最大ID, SQL為: select max_id, step, version from id_generator where biz_type = ‘**********’;
  2. 執行個體中設定ID範圍範圍為[max_id, max_id + step]
  3. 更新資料庫中記錄, SQL為: update table id_generator set max_id = max_id + step +1, version = version + 1 where biz_type = ‘**********’ and version = version(第一步中取出的);
  4. 如果第三步更新失敗, 則說明發生沖突, 從第一步開始重試, 直到第三步成功;
3.4.3.2 悲觀鎖實作
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 '業務類型',
  version int(20) NOT NULL COMMENT '版本号',
  PRIMARY KEY (`id`)
) 

           

執行個體取ID的步驟:

  1. 開啟事務;
  2. 從資料庫中擷取目前最大ID, SQL為: select max_id, step from id_generator where biz_type = ‘**********’ for update;
  3. 執行個體中設定ID範圍範圍為[max_id, max_id + step]
  4. 更新資料庫中記錄, SQL為: update table id_generator set max_id = max_id + step +1, version = version + 1 where biz_type = ‘**********’;
  5. 送出事務

3.5 Redis/Zookeeper

采用中間件的方式, Redis和Zookeeper都可以實作, 不過Zookeeper我很少使用,這裡不再讨論,其原理和Redis方式基本相同

3.5.1 Redis方式原理

3.5.1.1 強依賴redis方案

利用redis的 incr指令實作ID的原子性自增。

127.0.0.1:6379> set seq_id 1     // 初始化自增ID為1
OK
127.0.0.1:6379> incr seq_id      // 增加1,并傳回遞增後的數值
(integer) 2
           
3.5.1.2 弱依賴redis方案

原理和資料庫号段模式類似, redis中自增的是号段的起始值, 執行個體内緩存一個ID範圍, 這裡不再贅述.

3.5.2 優缺點

優點

  • 實作簡單
  • 性能比資料庫自增ID的方式要高

缺點

  • 依賴redis, 系統需要引入中間件,增加了運維等成本;
  • redis需考慮持久化, redis有兩種持久化方式RDB和AOF. RDB會定時打一個快照進行持久化,假如連續自增但redis沒及時持久化,而這會Redis挂掉了,重新開機Redis後會出現ID重複的情況; AOF會對每條寫指令進行持久化,即使Redis挂掉了也不會出現ID重複的情況,但由于incr指令的特殊性,會導緻Redis重新開機恢複的資料時間過長。

3.5.3 實作(redis自增模式的實作)

先設定RedisTemplate:

@Bean
	public RedisTemplate<String, Serializable> getDefaultRedisTemplate(RedisConnectionFactory cf, RedisSerializer<?> rs) {
		RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<String, Serializable>();
		redisTemplate.setConnectionFactory(cf);
		redisTemplate.setDefaultSerializer(rs);
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		return redisTemplate;
	}

           

接下來實作ID生成邏輯:

public long generate(String key,int increment) {
		RedisAtomicLong counter = new RedisAtomicLong(key, mRedisTemp.getConnectionFactory());
		return counter.addAndGet(increment);
	}

           

3.6 雪花算法

Twitter公司開源的一種算法, 全局唯一并且趨勢遞增

3.6.1 原理

分布式唯一ID分布式唯一ID

雪花算法生成的是8位元組64bit長度的數字(long類型), 能夠保證趨勢遞增的ID生成, 而且生成效率極高

雪花算法組成

  • 第1位: 占用1bit,其值始終是0,保證生成的ID是正數;
  • 第2- 42位: 時間戳, 占用41bit,精确到毫秒,總共可以容納約69年的時間;
  • 第43-52位: 工作機器id, 占用10bit,其中高位5bit是資料中心ID,低位5bit是工作節點ID,做多可以容納1024個節點;
  • 第53-64位:序列号,占用12bit,每個節點每毫秒0開始不斷累加,最多可以累加到4095,一共可以産生4096個ID。

SnowFlake算法在同一毫秒内最多可以生成的ID數量為: 1024 * 4096 = 4194304

3.6.2 優缺點

優點

  • 基于時間戳,可以保證基本有序遞增
  • 不依賴第三方的庫或者中間件, 在執行個體上生成, 可以保證高可用
  • 生成效率極高, 滿足高性能的要求

缺點

  • 依賴機器時間, 如果發生時鐘回退, ID可能重複

3.6.3 實作

/**
 * 雪花算法
 *
 * @author Young
 * @Date 2021-05-22 17:04
 */
public class SnowflakeIdGenerator {

    /**
     * 開始時間截 (這個用自己業務系統上線的時間)
     */
    private static final long START_TIMESTAMP = 1575365018000L;

    /**
     * 機器id所占的位數
     */
    private static final long WORKER_ID_BITS_LENGTH = 10L;

    /**
     * 支援的最大機器id,結果是31 (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
     */
    private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS_LENGTH);

    /**
     * 序列在id中占的位數
     */
    private static final long SEQUENCE_BITS_LENGTH = 12L;

    /**
     * 機器ID向左移12位
     */
    private static final long WORKER_ID_SHIFT_LENGTH = SEQUENCE_BITS_LENGTH;

    /**
     * 時間截向左移22位(10+12)
     */
    private static final long TIMESTAMP_LEFT_SHIFT_LENGTH = SEQUENCE_BITS_LENGTH + WORKER_ID_BITS_LENGTH;

    /**
     * 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095)
     */
    private static final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS_LENGTH);

    /**
     * 工作機器ID(0~1024)
     */
    private long workerId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的時間截
     */
    private long lastTimestamp = -1L;


    /**
     * 構造函數
     *
     * @param workerId 工作ID (0~1024)
     */
    public SnowflakeIdGenerator(long workerId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        this.workerId = workerId;
    }

    // ==============================Methods==========================================

    /**
     * 獲得下一個ID (該方法是線程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = currentTimestampMillis();

        // 時鐘回退處理, 時鐘回退一般是10ms内
        if (timestamp < lastTimestamp) {
            // 如果目前時間小于上一次ID生成的時間戳,說明系統時鐘回退過, 這個時候取上次時間戳作為目前時間
            timestamp = lastTimestamp;
        }

        if (lastTimestamp == timestamp) {
            // 如果是同一時間生成的,則進行毫秒内序列
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一個毫秒,獲得新的時間戳
                timestamp = nextTimestampMillis(lastTimestamp);
            }
        } else {
            //時間戳改變,毫秒内序列重置
            sequence = 0L;
        }
        // 更新上次生成ID的時間截
        lastTimestamp = timestamp;

        //移位并通過或運算拼到一起組成64位的ID
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT_LENGTH)
                | (workerId << WORKER_ID_SHIFT_LENGTH)
                | sequence;
    }

    /**
     * 阻塞到下一個毫秒,直到獲得新的時間戳
     *
     * @param lastTimestamp 上次生成ID的時間截
     * @return 目前時間戳
     */
    private long nextTimestampMillis(long lastTimestamp) {
        long timestamp = currentTimestampMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = currentTimestampMillis();
        }
        return timestamp;
    }

    /**
     * 傳回以毫秒為機關的目前時間
     *
     * @return 目前時間(毫秒)
     */
    private long currentTimestampMillis() {
        return System.currentTimeMillis();
    }

}
           

3.7 美團Leaf算法

推薦原系統的設計和開發者寫的Leaf——美團點評分布式ID生成系統, 已經非常詳細了, 小的就不再班門弄斧了

另外這篇也值得一看 Leaf:美團的分布式唯一ID方案深入剖析

美團Leaf算法有兩種模式:

  • segment模式: 即資料庫号段模式, 優缺點在上文已經讨論過了;
  • snowflake模式: 其主要有以下幾點優化
    • 解決時鐘回退問題: 周期性上傳執行個體時間戳到zookeeper中, 生成ID時擷取的時間戳會與Zookeeper的時間戳比較, 但是并沒有完全解決問題, 如果時鐘回退超過5ms還是會抛出異常;
    • 弱依賴zookeeper, 采用zookeeper持久化節點資訊(即雪花算法中的執行個體節點資訊), 但是引入zookeepery也是需要成本的

3.8 百度uid-generator

  • UidGenerator
  • UidGenerator:百度開源的分布式ID服務(解決了時鐘回撥問題)
  • 百度開源分布式id生成器uid-generator源碼剖析

百度uid-generator也是類snowflake算法, 其主要特點有:

  • 調整雪花算法的位數, 機器節點數增大
  • 弱依賴時間戳, 采用目前時間(秒)和系統上線時間的內插補點
  • 加入緩存機制, 更高的性能
  • 執行個體資訊從資料庫中擷取, 每次重新開機執行個體序号 + 1

3.9 TinyId

TinyId

滴滴開源的Tinyid如何每天生成億級别的ID?

  • Tinyid是用Java開發的一款分布式id生成系統,基于資料庫号段算法實作, 簡單來說是資料庫中儲存了可用的id号段,tinyid會将可用号段加載到記憶體中,之後生成id會直接記憶體中産生。
  • 可用号段在第一次擷取id時加載,如目前号段使用達到一定量時,會異步加載下一可用号段,保證記憶體中始終有可用号段。(如可用号段11000被加載到記憶體,則擷取id時,會從1開始遞增擷取,當使用到一定百分比時,如20%(預設),即200時,會異步加載下一可用号段到記憶體,假設新加載的号段是10012000,則此時記憶體中可用号段為2001000,10012000),當id遞增到1000時,目前号段使用完畢,下一号段會替換為目前号段。依次類推。
分布式唯一ID分布式唯一ID

TinyId的主要特點有:

  • 采用資料庫号段模式
  • 使用了雙緩存的邏輯
  • 多DB支援(資料庫叢集模式)

4 分布式ID生成方案總結

  • 分布式ID生成方案大緻分為2種, 一是基于資料庫或者redis的生成方案, 二是類雪花算法生成方案
  • 雪花算法生成方案中, 存在時鐘回退問題, 時鐘回退的問題可以使用記錄下上次生成的時間戳(引入資料庫或者中間件), 當生成ID時對比下目前時間和記錄的時間戳, 如果發生時鐘回退,那麼抛出異常或者将阻塞等待.
  • 資料庫或者redis的生成方案中, 存在難以擴充和資料庫性能問題, 衍生出資料庫叢集模式的方案和号段模式的方案, 号段模式為了防止耗盡時的高并發, 一般采用雙緩存的解決方案.

5 參考資料

  • 分布式ID之UUID适合做分布式ID嗎
  • UUID/GUID介紹、生成規則及生成代碼
  • 分布式唯一 ID 生成方案,有點全!
  • 分布式唯一ID生成方案
  • 一口氣說出9種分布式ID生成方式,面試官有點懵了
  • 這可能是講雪花算法最全的文章
  • 分布式ID神器之雪花算法簡介
  • Leaf——美團點評分布式ID生成系統
  • UidGenerator
  • UidGenerator:百度開源的分布式ID服務(解決了時鐘回撥問題)
  • 百度開源分布式id生成器uid-generator源碼剖析
  • TinyId
  • 滴滴開源的Tinyid如何每天生成億級别的ID?