自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

新來的技術女總監(jiān),推薦的分布式 ID 真香!

系統(tǒng)
本文分析了幾種常見的分布式 ID 生成方式,具體選用哪種可以根據(jù)自身業(yè)務而定。

在復雜的分布式系統(tǒng)中,常常需要一個全局唯一的 ID來標識數(shù)據(jù),消息或者請求,比如:訂單號,消息的唯一標識,接口的冪等ID等等。今天,我們一起來聊聊幾種常見的分布式ID 生成方式。

一、分布式ID具備什么條件?

分布式 ID,通常建議具備以下 4個條件:

1. 全局唯一

全局唯一,是指在支撐的業(yè)務范圍內(nèi)能保持全局唯一,這里的全局范圍可大可小,大到成千上萬個部門的集團,小到某個團隊內(nèi)的幾個系統(tǒng), 所以在設計分布式 ID的時候,一定要清晰地定位受眾范圍。

2. 數(shù)量夠用

分布式ID,一定要保證數(shù)量夠用,通常需要根據(jù)公司具體業(yè)務來評估,在一定時間范圍內(nèi)會消耗多個ID, 比如,用于訂單號,需要預估每年會產(chǎn)生多少訂單,消耗多少個訂單號,需要支撐幾年的業(yè)務使用。

3. 安全性

分布式 ID應該是安全的,防止惡意用戶預測或推斷出其他ID,如果業(yè)務對于這種預測不敏感可以忽略。比如,用于接口冪等使用,只要唯一就 ok。

4. 有序性

通常建議分布式 ID具備有序性,方便業(yè)務排序使用。

二、生成算法

1. UUID

UUID,是 Universally Unique Identifier,通用唯一識別碼的縮寫,共128-bit(2^128個),通常表示成 32個十六進制數(shù)字,并且以“-”分隔成五組(8bit-4bit-4bit-4bit-12bit), 比如:62f77d2d-aefe-41ba-b0f8-4c4c39ab670e。

UUID有 5種常見的生成方式,這里總結了構成,用途和特點:

(1) 基于時間戳和 MAC地址

  • 構成:基于當前時間戳、時鐘序列和機器的MAC地址。
  • 用途:可以確??缈臻g(不同機器)和時間的唯一性。
  • 特點:可能會暴露生成 UUID的機器地址和時間。

Java示例代碼,需要依賴 java-uuid-generator 工具:

import com.fasterxml.uuid.Generators;
public static void generateUUID() {
  UUID uuid = Generators.timeBasedGenerator().generate();
  System.out.println(uuid); // 比如:de02864c-eda8-11ee-a793-dd2f40a50976
}

(2) DCE安全的UUID

  • 構成:與方法1類似,但將一部分空間用于包含 POSIX的 UID或 GID。
  • 用途:用于DCE(分布式計算環(huán)境)的安全性。
  • 特點:并不常用,和方法1相似,但提供了額外的用戶或組識別信息。

(3) 基于名字的MD5散列

  • 構成:使用名字空間(Namespace)和特定名字生成 MD5散列。java.util.UUID 提供了這種方式,需要傳入key,使用比較少。
  • 用途:為相同名字生成相同的 UUID,但不同名字空間下的相同名字將生成不同的 UUID。
  • 特點:相同輸入產(chǎn)生相同輸出,但由于 MD5容易被破解,不安全,因此現(xiàn)在使用較少。

Java代碼示例:

import java.util.UUID;
public static void generateUUID() {
  String key = "xxxxx"; // 自己指定的key
  UUID uuid3 = UUID.nameUUIDFromBytes(key.getBytes()).toString();
  System.out.println(uuid); // 比如:ea416ed0-759d-36a8-9e58-f63a59077499
}

(4) 隨機生成

  • 構成:使用隨機數(shù)或偽隨機數(shù)生成,java.util.UUID 是基于這種方式,使用比較多。
  • 用途:不需要基于時間或名字,僅需要快速生成UUID。
  • 特點:簡單,沒有時間順序,不具備可預測性。

Java代碼示例:

import java.util.UUID;
public static void main(String[] args) {
  String uuid = UUID.randomUUID().toString();
  System.out.println(uuid); //比如:ea416ed0-759d-36a8-9e58-f63a59077499
}

(5) 基于名字的 SHA-1散列

  • 構成:與方法3類似,使用 SHA-1散列算法代替 MD5。
  • 用途:給相同名字生成相同的UUID,通常用于避免 MD5弱點的場景。
  • 特點:比方法3的 MD5更安全,但SHA-1目前也已被認為不夠安全,比較少使用。

Java代碼示例:

import java.security.MessageDigest;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;

public static UUID generateUUID(String namespace, String name) {
    try {
        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
        sha1.update(namespace.getBytes());
        byte[] result = sha1.digest(name.getBytes());

        // Set the version to 5
        result[6] &= 0x0f; // clear version
        result[6] |= 0x50; // set to version 5

        // Set the variant to 2
        result[8] &= 0x3f; // clear variant
        result[8] |= 0x80; // set to IETF variant

        ByteBuffer buffer = ByteBuffer.wrap(result);
        buffer.order(ByteOrder.BIG_ENDIAN);
        returnnew UUID(buffer.getLong(), buffer.getLong());
    } catch (Exception e) {
        thrownew RuntimeException("Unable to generate Version 5 UUID", e);
    }
}
// 使用namespace和name生成Version 5 UUID
UUID uuid5 = generateType5UUID("namespace", "name"); // 比如:c717c842-f1d0-5079-a123-4a86d058c64f

到此,我們對 UUID有了一個全面的認識,最后,總結下 UUID的優(yōu)缺點以及一些使用建議:

優(yōu)點:

  • 生成方式簡單,各種語言都有成熟的類庫
  • 本地生成,性能高,不依賴網(wǎng)絡
  • 隨機機行強,不容被預測

缺點:

  • 無序,不利于排序,用于 MySQL主鍵時對索引不夠友好
  • 32位,字符串太長,存儲需要消耗更多的空間
  • 具體生成 UUID算法的缺點

使用建議:

  • 日常工作為了使用更美觀,一般會把 UUID中的 “-”去掉
  • 如果對 UUID的缺點沒有任何要求,完全可以采用這種方式來生成分布式 ID,比如,作為接口的冪等ID
  • UUID有重復的概率,但是幾乎可以忽略不計

2. ULID

ULID 是 Universally Unique Lexicographically Sortable Identifier的縮寫,它是一種全局唯一、按字典序可排序的標識符。

ULID 由 48-bit的時間戳 + 80-bit 的隨機數(shù)組成,時間戳表示自 1970 年 1 月 1 日 UTC 起的毫秒數(shù),可以支持大約 280 年的時間范圍。

下面借助三方庫 Sulky ULID 演示如何使用 Java 生成 ULID:

import de.huxhorn.sulky.ulid.ULID;
public class ULIDTest {
    public static void main(String[] args) {
        ULID ulid = new ULID();
        String id = ulid.nextULID();
        System.out.println("ULID: " + id); // 01HTBBBDYK0YMQ8X1Z50QGDH9E
    }
}

最后總結下 ULID 的優(yōu)缺點:

優(yōu)點:

  • 可排序:ULID  由 48-bit 時間戳開頭,可以按照生成時間進行排序
  • 高性能:由于 ULID 使用時間戳和隨機數(shù),生成速度較快。

缺點:

  •  ULID 依賴于時間戳,極端情況可能會出現(xiàn)時間回撥導致 ID沖突

3. 基于 Redis

在 Redis中,可以通過 INCR 和 INCRBY 這樣的自增原子命令,來實現(xiàn)有序的分布式ID:

下面總結下 Redis的優(yōu)缺點:

優(yōu)點:

  • 高性能
  • 生成的 ID有序

缺點:

  • 需要增加 Redis的成本
  • 強依賴 Redis,因此需要保證 Redis服務的穩(wěn)定性
  • 生成的ID 有序,對于敏感業(yè)務,ID容易被預測

4. 基于MySQL

通過 MySQL數(shù)據(jù)表的主鍵(int 或 bigint)自增來生成分布式 ID,因此,首先來看看 int 和 bigint 類型支持的數(shù)據(jù)范圍。

int(或 integer):

  • 無符號的范圍是 0 到 4294967295(42億多)
  • 有符號的范圍是 -2147483648 到 2147483647

bigint:

  • 無符號的范圍是 0 到 18446744073709551615
  • 有符號的范圍是 -9223372036854775808 到 9223372036854775807

假如每天消耗 1億個,使用總年數(shù):18446744073709551615 / 100,000,000 / 365 ≈ 505061753 年。

假如每天消耗 1萬億個,使用總年數(shù):18446744073709551615 / 100,000,000,000,0 / 365 ≈ 50506 年(5萬年)。

使用 MySQL的 bigint,就算業(yè)務體量每天消耗一萬億個 ID,也需要 5萬年才能消耗完,絕對夠用。

接下來分析 2種常見生成方式:

(1) MySQL乞丐版

首先,創(chuàng)建一張 distributed_ids表,用于記錄已經(jīng)生成的 ID(如果 ID不想從 1開始,可以在創(chuàng)建表時給主鍵 ID設置一個起始值),表設計如下:

CREATE TABLE`distributed_ids` (
`id`BIGINT(20) NOTNULL AUTO_INCREMENT,
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- 或者
CREATETABLE`distributed_ids` (
`id`BIGINT(20) NOTNULL AUTO_INCREMENT,
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT 1000000; -- 1000000可以設置成具體的

接著,我們需要包裝一個 ID生成的服務對外提供給業(yè)務使用,整體架構思路圖如下:

下面為對外提供服務核心步驟的 java偽代碼:

@Controller
public class IdGeneratorController {
    @GetMapping("/generateId")
    public Long generateId() {
        Long id = null;
        synchronized (this) {
             // generate id;  sql: UPDATE distributed_ids SET id = id + 1(step);
            // select max id;  sql: SELECT id FROM distributed_ids ORDER BY id desc limit 1;
        }
        return id;
    }
}

到此,一個簡單的 ID生成系統(tǒng)就完成了,總結下該方案的優(yōu)缺點:

優(yōu)點:

  • 實現(xiàn)簡單,維護成本低
  • ID單調(diào)遞增
  • 可以用較少的成本實現(xiàn)體量較小的業(yè)務需求

缺點:

  • 每次獲取 ID時候需要進行兩次 DB操作
  • 強依賴于 DB,DB性能以及穩(wěn)定性直接影響 ID生成系統(tǒng)的穩(wěn)定性,而且這里有 DB單節(jié)點的風險

對于缺點中單臺 MySQL存在性能問題,我們自然會想到增加一臺 MySQL,一臺使用奇數(shù)自增(1,3,5,7,2n-1),一臺使用偶數(shù)自增(2,4,6,8,2n),這樣是不是就可以分攤壓力了?如果 2臺 MySQL還不夠用,那我們可以使用 N臺,每臺的初始值依次為 1, 2, 3 … N-1,步長是N(機器的數(shù)量),整個架構思路圖如下:

仔細觀察上述架構圖可以發(fā)現(xiàn),對于 MySQL節(jié)點選擇其實本質(zhì)上是一個 Hash算法,因此,該方案存在 Hash算法典型的缺點:水平擴容困難,擴容時,需要涉及到大量的數(shù)據(jù)遷移。

那么,有沒有一個更優(yōu)的設計方案,既能減少對 DB的操作,又能避免 DB的性能瓶頸?

(2) MySQL號段版

號段版是指每次獲取一批 ID(比如 1000,10000),而不是一個 ID,然后在內(nèi)存中去分發(fā)號段內(nèi)的號碼。對此,表結構該如何設計呢?

這里我們引入了號段最大值(max_id) 和步長(step)兩個概念,max_id是指表中末次獲取號段時的最大 ID號,step是指每次獲取多少個 ID。比如,初始值是 1,第一次獲取 1000個ID,那 max_id=1000, step=1000,第二獲取 1000個ID,那么 max_id=2000, step=1000,表結構設計如下:

-- 號段 + 步長step
CREATETABLE`distributed_ids` (
`max_id`BIGINT(20) NOTNULL AUTO_INCREMENT,
`step`INT(11) NOTNULL,
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT 1; -- 1可以設置成具體的起始值

整體架構思路圖:

對于這個方案中的 step設置多大比較合理,一般會參考流量高峰期的是QPS/TPS, 假設高峰期的 QPS是 1000(1s會消耗 1000個ID), 如果想取用一次號段持續(xù)使用 10分鐘,那么 step=1000 * 60 * 10,即 step是 QPS的 600倍,以此類推, 也可以參考以往業(yè)務流量分布,確認峰值一般會維持多長時間,根據(jù)這個時間來設定一個合理的 step,這樣就可以避免業(yè)務高峰期頻繁去操作 DB獲取號段。

總結下方案的優(yōu)缺點:

優(yōu)點:

  • 一次批量獲取 ID,大大降低了對 MySQL的壓力,比如QPS是 1萬,如果 step設置為1000, 那么對 MySQL的壓力就從 10000降到 10
  • step可以更具業(yè)務壓力靈活調(diào)整,可以將此參數(shù)設置為配置變量

缺點

  • DB不可用導致整個ID生成系統(tǒng)不可用
  • ID規(guī)律性太強,很容易識別語義,比如,用于訂單號時,可以根據(jù)某一個訂單號輕易地猜測出其他訂單號

對于缺點1,可以采用高可用容災方案:主從(一主多從) + 多活(2 地 3活),當主節(jié)點掛了,需要將從節(jié)點切換成主節(jié)點,當機房A 出現(xiàn)問題, 可以切換到機房B,這樣充分保證了服務的高可用,思路看起來簡單,但實施起來比較困難,特別是主從切換的時候,如何保證數(shù)據(jù)一致性是一個比較大的挑戰(zhàn)。

對于缺點2,可以采用雪花算法等方案來生成隨機且遞增的 ID,在下文的三方工具里有很好的實現(xiàn)。

整個高可用容災方案如下圖:

5. 雪花算法

Snowflake,雪花算法是由 Twitter開源的分布式 ID生成算法,整個ID包含 64-bit,對應 Java的 Long類型,如下圖:

為了更好地說明,本文將 64-bit 分成A, B, C, D 4部分:

  • A部分,占用 1-bit,代表符號位,其值始終是0
  • B部分,占用 41-bit,代表時間戳,可表示 2^41 個數(shù),每個數(shù)代表毫秒,雪花算法可用的時間年限大概是 69年。
  • C部分,占用 10-bit,代表機器數(shù)或者 IDC(數(shù)據(jù)中心)+ 機器數(shù),如果是機器數(shù),即 2^10 = 1024 臺機器,如果是 IDC+機器數(shù), 一般 5-bit 用于 IDC(最大 32個IDC)C,5-bit用于工作機器(最大 32臺機器),這里可以根據(jù)自身業(yè)務靈活調(diào)整。
  • D部分,占用 12-bit,代表自增序列,可表示 2^12 = 4096個數(shù)。

基于上述的劃分可以看出,每豪秒能產(chǎn)生 1024 * 4096個有序不重復的ID。

最后,總結下雪花算法的優(yōu)缺點:

優(yōu)點:

  • 本地生成,不依賴數(shù)據(jù)庫等第三方系統(tǒng),穩(wěn)定性高
  • ID是趨勢遞增,對于敏感業(yè)務,不具備可預測性
  • 可以根據(jù)自身業(yè)務特性靈活分配 bit位

缺點:

  • 強依賴機器時鐘,如果時鐘回撥,可能會導致 ID重復

6. 三方工具

(1) 美團的 Leaf

Leaf是美團開源的分布式 ID生成工具,它包含 Leaf-segment 和 Leaf-snowflake兩種方案, Leaf-segment是基于 MySQL數(shù)據(jù)庫,它是針對上述 MySQL方案的更優(yōu)秀設計,Leaf-snowflake 是基于雪花算法,并且解決雪花算法中時鐘回撥的問題。

詳情參考官方文檔:https://tech.meituan.com/2017/04/21/mt-leaf.html

(2) 滴滴的TinyID

Tinyid是用Java開發(fā)的一款分布式id生成系統(tǒng),基于數(shù)據(jù)庫號段算法實現(xiàn),Tinyid擴展了leaf-segment算法,支持了多db(master),同時提供了java-client(sdk)使id生成本地化,獲得了更好的性能與可用性。

詳情參考官方文檔:https://github.com/didi/tinyid/wiki

(3) 百度-UidGenerator

UidGenerator 百度開源的一款用 Java語言實現(xiàn),基于 Snowflake算法的唯一ID生成器。包含 DefaultUidGenerator 和 CachedUidGenerator 2種實現(xiàn)方式:

  • DefaultUidGenerator 是基于時間戳和機器位還有序列號的生成方式,和雪花算法很相似,對于時鐘回撥也只是拋異常處理。僅有一些不同,如以秒為為單位而不再是毫秒和支持Docker等虛擬化環(huán)境。
  • CachedUidGenerator 是使用 RingBuffer 緩存生成的id。數(shù)組每個元素成為一個slot。RingBuffer容量,默認為Snowflake算法中sequence最大值(2^13 = 8192)??赏ㄟ^ boostPower 配置進行擴容,以提高 RingBuffer 讀寫吞吐量。

詳情參考官方文檔:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

三、總結

本文分析了幾種常見的分布式 ID生成方式,具體選用哪種可以根據(jù)自身業(yè)務而定:

  • UUID簡單高效,但是無序,如果作為 MySQL的ID主鍵,對于索引不太友好
  • ULID 很好的解決了UUID無序的問題,極端情況下存在時間回撥導致ID重復的風險
  • 基于 Redis生成的 ID有序,性能高,需要引入和維護 Redis
  • MySQL 乞丐版,實現(xiàn)簡單,但是不適合大流量的場景
  • MySQL 號段版,適合大流量的場景,但是,當 MySQL主節(jié)點故障時,主從切換需要保持數(shù)據(jù)的一致性
  • 雪花算法,有序且不易預測,算法很優(yōu)秀,不過需要解決時鐘回撥的問題
  • 三方工具,經(jīng)過生產(chǎn)環(huán)境的考驗,是很不錯的方式,如果不想重復造輪子,優(yōu)先推薦該方式
責任編輯:趙寧寧 來源: 猿java
相關推薦

2024-09-18 00:00:10

UUID識別碼標志符

2022-03-24 07:51:27

seata分布式事務Java

2022-06-16 07:31:15

MySQL服務器服務

2019-10-10 09:16:34

Zookeeper架構分布式

2023-07-26 00:46:25

GitMain主分支

2020-07-15 16:50:57

Spring BootRedisJava

2024-10-09 14:14:07

2025-04-30 10:44:02

2024-01-10 08:02:03

分布式技術令牌,

2020-02-13 08:08:23

禁止使用Lombok

2020-02-12 14:03:25

實用Java工具

2023-10-26 18:10:43

分布式并行技術系統(tǒng)

2017-07-01 16:02:39

分布式ID生成器

2024-01-08 08:05:08

分開部署數(shù)據(jù)體系系統(tǒng)拆分

2019-06-19 15:40:06

分布式鎖RedisJava

2024-01-09 08:00:58

2019-09-05 13:06:08

雪花算法分布式ID

2023-03-05 18:23:38

分布式ID節(jié)點

2019-05-13 09:20:02

京東云RedisMongoDB

2014-11-19 10:12:29

Java分布式緩存
點贊
收藏

51CTO技術棧公眾號