分佈式ID - Snowflake

今天要跟大家介紹如果在分布式的系統上,我們要如何產生系統的唯一 ID 呢?
目前常見的幾個方式:

  • 使用 UUID
  • 使用資料庫控制
  • 實作 Snowflake

UUID 由於不具可讀性,無法有序排序且過長,而資料庫控制的瓶頸很容易會卡在資料庫上。
因次今天要大家介紹的方法是 Snowflake

Quick Start

Snowflake 是 Twitter 在 2010 年公佈的一個分布式 ID 的演算法。原始代碼是使用 Scala 撰寫。
目前 Snowflake 已經不公開維護了,不過大家還是可以在 GitHub 下載 Source Code

大家可以從上面的源碼中查看 README,可以看到,Twitter 設計的 UUID 是透過 64 bits 生成的一個唯一 ID (long)
整個 ID 組成包含

  • time - 41 bits (millisecond precision w/ a custom epoch gives us 69 years)
  • configured machine id - 10 bits - gives us up to 1024 machines
  • sequence number - 12 bits - rolls over every 4096 per machine (with protection to avoid rollover in the same ms)

有很多人有將 Twitter 的 Snowflake 演算法實作成 Java 的版本。如:

不過從上面大家可以看到,代碼中我們在創建 Snowflake 時,需要帶入類似機器碼的資訊來生成 UUID

1
SnowFlake snowFlake = new SnowFlake(2, 3);

如果是一個分散式的程式架構,那我們要如何處理這邊呢?
這裡我們可以參考百度出的UidGenerator,來幫我們處理這個問題。

百度的 UidGenerator

百度的方式基本上也是依照 Twitter 提出的 Snowflake 演算法進行調整。

不過他在資料庫中,加入了一張 Table 去紀錄我們所謂的 merchant id
Table Schema 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
DROP TABLE IF EXISTS WORKER_NODE;
CREATE TABLE WORKER_NODE
(
ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
PORT VARCHAR(64) NOT NULL COMMENT 'port',
TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
MODIFIED TIMESTAMP NOT NULL COMMENT 'modified time',
CREATED TIMESTAMP NOT NULL COMMENT 'created time',
PRIMARY KEY(ID)
)
COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;

不知道大家有沒有發現,使用百度的 UidGenerator 並沒有很方便,因為我們還需要將他的 Source Code 放入我們的 Project 中來做使用。

那有沒有更快速便捷的方式呢?

UidGenerator Springboot Starter

UidGenerator Springboot Starter主要基於 UidGenerator 調整為 spring-boot-starter 方式。

使用方式如下:

  • 加入 maven
    pom.xml

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.wujun234</groupId>
    <artifactId>uid-generator-spring-boot-starter</artifactId>
    <version>1.0.2.RELEASE</version>
    </dependency>
  • 可選配置
    application.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    uid:
    timeBits: 30 # 时间位, 默认:30
    workerBits: 16 # 机器位, 默认:16
    seqBits: 7 # 序列号, 默认:7
    epochStr: "2019-02-20" # 初始时间, 默认:"2019-02-20"
    enableBackward: true # 是否容忍时钟回拨, 默认:true
    maxBackwardSeconds: 1 # 时钟回拨最长容忍时间(秒), 默认:1
    CachedUidGenerator: # CachedUidGenerator相关参数
    boostPower: 3 # RingBuffer size扩容参数, 可提高UID生成的吞吐量, 默认:3
    paddingFactor: 50 # 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50
    #scheduleInterval: 60 # 默认:不配置此项, 即不实用Schedule线程. 如需使用, 请指定Schedule线程时间间隔, 单位:秒
  • 測試
    UuidTests.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UuidTests {

    @Resource
    private UidGenerator defaultUidGenerator;

    // @Resource
    // private UidGenerator cachedUidGenerator;

    @Test
    public void testSerialGenerate() {
    // Generate UID
    for(int i=0; i<=1000; i++) {
    long uid = defaultUidGenerator.getUID();
    System.out.println(defaultUidGenerator.parseUID(uid));
    }
    }

    }

Unit 結果驗證圖

資料庫的 WORKER_NODE 數據

補充!

  • 這邊我們可以發現,如果是同一台 Server 如果 HOST_NAMEPORT 就不會新產生一筆資料。
  • 而我們也可以透過配置 application.yml 調整整個 UUID 想要配置的各個區塊的長度(時間、機器碼、流水號)等。

如果有其他問題,歡迎寄信討論!謝謝!

Reference

Twitter Snoflake
Java Snowflake
UidGenerator
UidGenerator Springboot Starter

謝謝您的支持與鼓勵

Ads