搞定了 6 種分佈式ID,分庫分表哪個適合做主鍵?

大家好,我是小富~

本文是《ShardingSphere5.x分庫分表原理與實戰》系列的第篇,目前系列的前幾篇製作成了PDF,需要的可以在文末獲取下載方式,持續更新中。今天咱們繼續一起來探究下,分佈式ID在分庫分表中起到的作用以及如何使用,ShardingSphere-jdbc中已經爲我們提供了多種分佈式主鍵ID生成策略。接下來將分別介紹這些策略的優缺點,看看它們在實際應用中的場景和效果。

                                小富的技術站:https://xiaofucode.com

爲什麼用分佈式主鍵ID

在傳統的單庫單表結構時,通常可以使用自增主鍵來保證數據的唯一性。但在分庫分表的情況下,每個表的默認自增步長爲1,這導致了各個庫、表之間可能存在重疊的主鍵範圍,從而使得主鍵字段失去了其唯一性的意義。

爲了解決這一問題,我們需要引入專門的分佈式 ID 生成器來生成全局唯一的ID,並將其作爲每條記錄的主鍵,以確保全局唯一性。通過這種方式,我們能夠有效地避免數據衝突和重複插入的問題,從而保障系統的正常運行。

除了滿足唯一性的基本要求外,作爲主鍵 ID,我們還需要關注主鍵字段的數據類型、長度對性能的影響。因爲主鍵字段的數據類型長度直接影響着數據庫的查詢效率和整體系統性能表現,這一點也是我們在選方案時需要考慮的因素。

內置算法

ShardingSphere 5.X版本後進一步豐富了其框架內部的主鍵生成策略方案。此前僅提供了UUIDSnowflake兩種策略,現在又陸續提供了NanoIDCosIdCosId-Snowflake三種策略。下面我們將逐個的過一下。

注意SQL中不要主動拼接主鍵字段(包括持久化工具自動拼接的)否則一律走默認的Snowflake策略!!!

ShardingSphere中爲分片表設置主鍵生成策略後,執行插入操作時,會自動在SQL中拼接配置的主鍵字段和生成的分佈式ID值。所以,在創建分片表時主鍵字段無需再設置 自增 AUTO_INCREMENT。同時,在插入數據時應避免爲主鍵字段賦值,否則會覆蓋主鍵策略生成的ID。

CREATE TABLE `t_order` (
  `id` bigint NOT NULL,
  `order_id` bigint NOT NULL,
  `user_id` bigint NOT NULL,
  `order_number` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
  `customer_id` bigint NOT NULL,
  `order_date` datetime DEFAULT NULL,
  `interval_value` varchar(125) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `total_amount` decimal(10,2) NOT NULL,
  PRIMARY KEY (`order_id`) USING BTREE
) ;

UUID

想要獲得一個具有唯一性的ID,大概率會先想到UUID,因爲它不僅具有全球唯一的特性使用還簡單。但並不推薦將其作爲主鍵ID。

  • UUID的無序性。在插入新行數據後,InnoDB無法像插入有序數據那樣直接將新行追加到表尾,而是需要爲新行尋找合適的位置來分配空間。由於ID無序,頁分裂操作變得不可避免,導致大量數據的移動。頻繁的頁分裂會導致數據碎片化(即數據在物理存儲上分散分佈)。這種隨機的ID分配過程需要大量的額外操作,導致頻繁的對數據進行無序的訪問,導致磁盤尋道時間增加。數據的無序性進一步加劇了數據碎片化,降低了數據訪問效率。

  • UUID字符串類型。字符串比數字類型佔用更多的存儲空間,對存儲和查詢性能造成較大的消耗;字符串類型的長度可變,可變長度的數據行會破壞索引的連續性,導致索引查找性能下降。

算法類型:UUID

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # UUID生成算法
          uu-id-gen:
            type: UUID
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            database-strategy:  # 分庫策略
              standard:
                sharding-column: order_id
                sharding-algorithm-name: t_order_database_mod
            table-strategy: # 分表策略
              standard:
                sharding-column: order_id
                sharding-algorithm-name: t_order_table_mod
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: uu-id-gen

NanoID

或許很多人都不熟悉 NanoID,它是一款用類似 UUID 生成唯一標識符的輕量級庫。不過,與 UUID 不同的是 NanoID 生成的字符串ID長度較短,僅爲21位。但仍然不推薦將它作爲主鍵ID,理由和UUID一樣。

算法類型:NANOID

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # nanoid生成算法
          nanoid-gen:
            type: NANOID
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: nanoid-gen

定製雪花算法

雪花算法是比較主流的分佈式ID生成方案,在 ShardingSphere 中的Snowflake算法生成的是 Long 類型的 ID,通常作爲默認的主鍵生成策略使用。

內置的雪花算法生成的ID主要由時間戳、工作機器IDworkId、序列號sequence三部分組成。

@Override
  public synchronized Long generateKey() {
      ..........
      return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
  }

定製 Snowflake 算法有三個可配置的屬性:

worker-id:工作機器唯一標識,單機模式下會直接取此屬性值計算ID,默認是0;集羣模式下則由系統自動生成,此屬性無效

max-vibration-offset:最大抖動上限值,範圍[0, 4096),默認是1。那麼如何理解這個屬性呢? 這個屬性是用來控制上邊生成雪花ID中的sequence。通過限制抖動範圍,同一毫秒內生成的ID中引入微小的變化,讓數據更均勻地分散到不同的分片上。

private void vibrateSequenceOffset() {
    sequenceOffset = sequenceOffset >= maxVibrationOffset ? 0 : sequenceOffset + 1;
}

若使用此算法生成值作分片值,建議配置此屬性。此算法在不同毫秒內所生成的 key 取模 2^n (2^n一般爲分庫或分表數) 之後結果總爲 0 或 1。爲防止上述分片問題,建議將此屬性值配置爲 (2^n)-1

max-tolerate-time-difference-milliseconds:最大容忍時鐘回退時間(毫秒)。服務器在校對時間時可能會發生時鐘回撥的情況(當前時間回退),由於根據時間戳參與計算ID,這可能導致生成相同的ID,而這對系統來說是不可接受的。

ShardingSphere 雪花算法針對時鐘回撥場景進行了處理,記錄最後一次生成ID的時間 lastMilliseconds,並與回撥後的當前時間 currentMilliseconds 進行比對。如果時間差超過了設置的最大容忍時鐘回退時間,系統將直接拋出異常;如果未超過,則系統會休眠等待兩者時間差的時長,核心原則確保不會發放重複的ID

@SneakyThrows(InterruptedException.class)
private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {
    if (lastMilliseconds <= currentMilliseconds) {
        return false;
    }
    long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;
    Preconditions.checkState(timeDifferenceMilliseconds < maxTolerateTimeDifferenceMilliseconds,
            "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastMilliseconds, currentMilliseconds);
    Thread.sleep(timeDifferenceMilliseconds);
    return true;
}

算法類型:SNOWFLAKE

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # 雪花ID生成算法
          snowflake-gen:
            type: SNOWFLAKE
            props:
              worker-id: # 工作機器唯一標識
              max-vibration-offset: 1024 # 最大抖動上限值,範圍[0, 4096)。注:若使用此算法生成值作分片值,建議配置此屬性。此算法在不同毫秒內所生成的 key 取模 2^n (2^n一般爲分庫或分表數) 之後結果總爲 0 或 1。爲防止上述分片問題,建議將此屬性值配置爲 (2^n)-1
              max-tolerate-time-difference-milliseconds: 10 # 最大容忍時鐘回退時間,單位:毫秒
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: snowflake-gen

CosId

CosId 是一個高性能的分佈式ID生成器框架,Shardingsphere 將其引入到自身的框架內,只簡單的使用了 CosId 算法。但目前親測 5.2.0版本該算法處於不可用狀態!!!我已經給官方提了issue,看看他們咋回覆吧

CosId 框架內提供了 3 種算法:

  • SnowflakeId: 單機 TPS 性能:409W/s , 主要解決時鐘回撥問題 、機器號分配問題並且提供更加友好、靈活的使用體驗。
  • SegmentId: 每次獲取一段 (Step) ID,來降低號段分發器的網絡IO請求頻次提升性能,提供多種存儲後端:關係型數據庫、Redis、Zookeeper 供用戶選擇。
  • SegmentChainId(推薦): SegmentChainId (lock-free) 是對 SegmentId 的增強。性能可達到近似 AtomicLong 的 TPS 性能 12743W+/s。

該算法使用對外提供了兩個屬性:

  • id-name:ID 生成器名稱。
  • as-string:是否生成字符串類型ID,將 long 類型 ID 轉換成 62 進制 String 類型(Long.MAX_VALUE 最大字符串長度11位),並保證字符串 ID 有序性。

算法類型:COSID

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # COSID生成算法
          cosId-gen:
            type: COSID
            props:
              id-name: share
              as-string: false
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: cosId-gen

CosId-Snowflake

CosId-Snowflake 是 CosId 框架內提供的 Snowflake 算法,它的實現原理和上邊的定製版雪花算法類似,ID主要也是由時間戳、工作機器ID、序列號sequence三部分組成。同樣處理了時鐘回撥等問題。

public synchronized long generate() {
    long currentTimestamp = this.getCurrentTime();
    if (currentTimestamp < this.lastTimestamp) {
        throw new ClockBackwardsException(this.lastTimestamp, currentTimestamp);
    } else {
        if (currentTimestamp > this.lastTimestamp && this.sequence >= this.sequenceResetThreshold) {
            this.sequence = 0L;
        }

        this.sequence = this.sequence + 1L & this.maxSequence;
        if (this.sequence == 0L) {
            currentTimestamp = this.nextTime();
        }

        this.lastTimestamp = currentTimestamp;
        long diffTimestamp = currentTimestamp - this.epoch;
        if (diffTimestamp > this.maxTimestamp) {
            throw new TimestampOverflowException(this.epoch, diffTimestamp, this.maxTimestamp);
        } else {
            return diffTimestamp << (int)this.timestampLeft | this.machineId << (int)this.machineLeft | this.sequence;
        }
    }
}

這個算法提供了兩個屬性:

  • epoch:固定的起始時間點,雪花ID算法的 epoch 變量值,默認值:1477929600000。用它的目的提高生成的ID的時間戳部分的可讀性、穩定性和範圍限制,使得生成的ID更加可靠和易於管理。
  • as-string:是否生成字符串類型ID,將 long 類型 ID 轉換成 62 進制 String 類型(Long.MAX_VALUE 最大字符串長度11位),並保證字符串 ID 有序性。

算法類型:COSID_SNOWFLAKE

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # cosId-snowflake生成算法
          cosId-snowflake-gen:
            type: COSID_SNOWFLAKE
            props:
              epoch: 1477929600000
              as-string: false
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: cosId-snowflake-gen

自定義分佈式主鍵

上邊咱們介紹了 ShardingSphere 內提供的 5 種生成主鍵的ID算法,這些算法基本可以滿足大部分的業務場景。不過,在某些情況下,我們可能會要求生成的ID具有特殊的含義或遵循特定的規則。ShardingSphere 也支持我們自定義生成主鍵ID,來滿足定製的業務需求。

實現接口

要實現自定義的主鍵生成算法,首先需要實現 KeyGenerateAlgorithm 接口,並實現內部 4 個方法, 其中有兩個方法比較關鍵:

  • getType():我們自定義的算法類型,方便配置使用;
  • generateKey():處理主鍵生成的核心邏輯,我們可以根據業務需求選擇合適的主鍵生成算法,比如美團的 Leaf、滴滴的 TinyId 等。
@Data
@Slf4j
public class SequenceAlgorithms implements KeyGenerateAlgorithm {

    // 這個方法用於指定我們自定義的算法的類型。它會返回一個字符串,表示所使用算法的類型,方便在配置和識別時使用。
    @Override
    public String getType() {
        // 返回算法類型表示
        return "custom";
    }

    // 這是生成主鍵的核心邏輯所在。在這個方法內部,我們可以根據業務需求選擇合適的主鍵生成算法,比如美團的Leaf、滴滴的TinyId等。這個方法的具體實現會根據所選算法的特點和要求來設計
    @Override
    public Comparable<?> generateKey() {
        return null;
    }

    @Override
    public Properties getProps() {
        return null;
    }
    // 這個方法用於初始化主鍵生成算法所需的資源或配置
    @Override
    public void init(Properties properties) {
    }
}

在引入外部的分佈式ID生成器時,應儘量遵循以下原則:

  • 全局唯一:必須保證ID是全局性唯一的,基本要求
  • 高性能:高可用低延時,ID生成響應要塊,否則反倒會成爲業務瓶頸
  • 高可用:100%的可用性是騙人的,但是也要無限接近於100%的可用性
  • 好接入:要秉着拿來即用的設計原則,在系統設計和實現上要儘可能的簡單

SPI 註冊

通過 SPI 方式加載我們自定義的主鍵算法,需要在 resource/META-INF/services 目錄下創建一個文件,文件名爲 org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm,並將我們自定義的主鍵算法的完整類路徑放入文件內,每行一個。在系統啓動時會自動加載到這個文件,讀取其中的類路徑,然後通過反射機制實例化對應的類,完成主鍵算法的註冊和加載。

resource
    |_META-INF
        |_services
           |_org.apache.shardingsphere.sharding.spi.KeyGenerateAlgorithm

配置使用

上邊完成了自定義算法的邏輯,使用上與其他的算法一致。只需將我們剛剛定義的算法類型 custom 配置上即可。

spring:
  shardingsphere:
    rules:
      sharding:
        key-generators:  # 分佈式序列算法配置
          # 自定義ID生成策略
          xiaofu-id-gen:
            type: custom
        tables:
          t_order:  # 邏輯表名稱
            actual-data-nodes: db$->{0..1}.t_order_${0..2} # 數據節點:數據庫.分片表
            key-generate-strategy: # 分佈式主鍵生成策略
              column: id
              keyGeneratorName: xiaofu-id-gen

當執行插入操作時,debug 看已經進入到了定義的主鍵算法內了。

總結

我們介紹了 ShardingSphere 的幾種內置主鍵生成策略以及如何自定義主鍵生成策略,市面上還有許多優秀的分佈式ID框架都可以整合進來,但具體選擇何種策略還是要取決於自身的業務需求。關於分佈式 ID 生成器,我曾經撰寫過一篇 一口氣說出 9種 分佈式ID生成方式,詳細介紹了多種生成器的優缺點,大家可以作爲參考。

案例GitHub地址https://github.com/chengxy-nds/Springboot-Notebook/tree/master/shardingsphere101/shardingsphere-sequence-algorithm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章