分布式系列-分布式ID


原創文章,轉載請標注。https://www.cnblogs.com/boycelee/p/15227230.html

一、數據庫自增(單實例)1、方案描述2、優點3、缺點二、數據庫集群模式1、方案描述2、優點3、缺點三、Redis優點缺點四、UUID優點缺點五、號段模式優點缺點六、雪花算法(SnowFlake)優點缺點七、Leaf號段模式優化雙Buffer解決時鍾問題參考

一、數據庫自增(單實例)

1、方案描述

基於數據庫自增ID(auto_increment)利用其來充當分布式ID。實現方式就是用一張表來充當ID生成器,當我們需要ID時,向表中插入一條記錄返回主鍵ID。

CREATE TABLE identity_table(
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `value` VARCHAR(50NOT NULL DEFAULT '' COMMENT 'value',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ID信息表';
<insert id="insertAndGetId" useGeneratedKeys="true" keyProperty="id">  
    insert into identity_table(value)  
    values(#{value})
</insert>

2、優點

(1)通過數據庫保證唯一性,實現簡單;

(2)數字ID,具備有序性。

3、缺點

(1)存在單點宕機風險;

(2)無法抗住高並發場景。

二、數據庫集群模式

1、方案描述

基於多實例數據庫主鍵自增,通過橫向擴展機器,解決單點數據庫的壓力。實現方式就是在自增ID(auto_increment)的基礎上,設置step增長步長,使得不同實例中的ID不會發生碰撞。

set @@auto_increment_offset = 0;     -- 起始值
set @@auto_increment_increment = 3;  -- 步長
1630223792570-3d087bcb-aa9d-4c22-88a8-b849e83a5282
1630223792570-3d087bcb-aa9d-4c22-88a8-b849e83a5282

2、優點

(1)利用水平擴展機器,解決單點問題;

3、缺點

(1)擴展實例,需要修改步長,操作復雜;

(2)產生ID需要依賴數據庫,數據庫的性能依然是瓶頸。

三、Redis

當使用數據庫來生成ID性能不夠要求的時候,我們可以嘗試使用Redis來生成ID。通過Redis的原子操作 INCR和INCRBY來實現。

可以使用Redis集群來獲取更高的吞吐量。假如一個集群中有3台Redis。可以初始化每台Redis的值分別是0,1,2,然后步長都是3。

1630338567842-e2b46247-1f39-4c9a-9745-76028cfb588f
1630338567842-e2b46247-1f39-4c9a-9745-76028cfb588f

優點

(1)不依賴於數據庫,性能優於數據庫;

(2)ID是有序的。

缺點

(1)強依賴於Redis,Redis宕機也會有風險;

(2)有I/O操作,網絡抖動會影響服務響應速度。

四、UUID

UUID 是由一組32位數的16進制數字所構成,是故 UUID 理論上的總數為16^32=2^128,約等於3.4 x 10123。也就是說若每納秒產生1百萬個 UUID,要花100億年才會將所有 UUID 用完。

UUID生成版本:

version 1date-time & MAC address
version 2date-time & group/user id
version 3, MD5 hash & namespace
version 4, pseudo-random number
version 5, SHA-1 hash & namespace

Java實現:

/**
     * Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
     *
     * The {@code UUID} is generated using a cryptographically strong pseudo
     * random number generator.
     *
     * @return  A randomly generated {@code UUID}
     */

    public static UUID randomUUID() {
        SecureRandom ng = Holder.numberGenerator;

        byte[] randomBytes = new byte[16];
        ng.nextBytes(randomBytes);
        randomBytes[6]  &= 0x0f;  /* clear version        */
        randomBytes[6]  |= 0x40;  /* set to version 4     */
        randomBytes[8]  &= 0x3f;  /* clear variant        */
        randomBytes[8]  |= 0x80;  /* set to IETF variant  */
        return new UUID(randomBytes);
    }

碰撞概率:

Java中的UUID使用的是版本4進行實現,128bit中有122bit是隨機產生的,產生的UUID重復概率非常低,所以在使用時可以不考慮此問題。

最終生成UUID:

123e4567-e89b-12d3-a456-426655440000

索引問題:

導致索引重排。

對於B+樹的結構:
(1)孩子數和key的數目相同

1630337385867-35d40ea1-5bc3-4cd3-b3cc-af4e15f56787
1630337385867-35d40ea1-5bc3-4cd3-b3cc-af4e15f56787

優點

(1)實現簡單;

(2)本地生成,無性能瓶頸;

(3)具備唯一性。

缺點

(1)ID是無序的;

(2)ID無特定含義;

(3)UUID是字符串且長度較長,存儲與查詢效率慢。

五、號段模式

號段模式可以理解為從數據庫批量的獲取自增ID,每次從數據庫取出一個號段范圍,例如 (1,1000] 代表1000個ID,具體的業務服務將本號段,生成1~1000的自增ID並加載到內存。

CREATE TABLE id_generator (
  id int(10NOT NULL,
  max_id bigint(20NOT NULL COMMENT '當前最大id',
  step int(20NOT NULL COMMENT '號段的布長',
  biz_type    int(20NOT NULL COMMENT '業務類型',
  version int(20NOT NULL COMMENT '版本號',
  PRIMARY KEY (`id`)

a. biz_type :代表不同業務類型

b. max_id :當前最大的可用id

c. step :代表號段的長度

d. version :是一個樂觀鎖,每次都更新version,保證並發時數據的正確性

img
img

等這批號段ID用完,再次向數據庫申請新號段,對max_id字段做一次update操作,update max_id= max_id + step,update成功則說明新號段獲取成功,新的號段范圍是(max_id ,max_id +step]。

架構圖:

1630289552285-6cfd8f45-b82e-46c1-a12a-e32eb0bbad5d
1630289552285-6cfd8f45-b82e-46c1-a12a-e32eb0bbad5d
update id_generator set max_id = #{max_id+step}, version = version + 1 where version = # {version} and biz_type = XXX

由於多業務端可能同時操作,所以采用版本號version樂觀鎖方式更新,這種分布式ID生成方式不強依賴於數據庫,不會頻繁的訪問數據庫,對數據庫的壓力小很多,查詢頻率減小到1/step。

優點

(1)ID號碼是趨勢遞增的,滿足數據庫存儲和查詢性能要求

(2)可用性高,即使ID生成服務器不可用,也能夠使得業務在短時間內可用。

(3)可以自定義max_id的大小,方便業務遷移,方便機器橫向擴張

缺點

(1)ID號碼不夠隨機,完整的順序遞增可能帶來安全問題;

(2)DB宕機可能導致整個系統不可用,仍然存在這種風險,因為號段只能撐一段時間。

六、雪花算法(SnowFlake)

SnowFlake算法是Twitter開源的分布式ID生成算法。生成長度為64bit的long型的數字作為全局唯一ID。

1630248359241-f223465b-d7b9-40cd-b4c2-bffca3673ae5
1630248359241-f223465b-d7b9-40cd-b4c2-bffca3673ae5

a. 1bit

第一個bit是符號位,生成的ID是正數,所以第一個bit是0。

**b. 41bit

**41bit可以表示2^41個毫秒值,相當於69年。

c. 10bit

10bit可以表示工作機器,5bit表示(2^5=32個)機房,剩余5bit表示(2^5=32台)機器。表示最多可以標識(2^10=1024台)機器

d. 12bit

用來標識同一毫秒內的(2^12 - 1 = 4095個)不同的id

優點

(1)高性能高可用:生成時不依賴於數據庫,完全在內存中生成;

(2)容量大:每秒中能生成數百萬的自增ID;

(3)ID自增:存入數據庫中,索引效率高。

缺點

(1)依賴系統時鍾,如果時間回調或改變,會造成ID沖突。

七、Leaf

號段模式優化

數據庫的I/O操作(更新、查詢)會是瓶頸,所以需要對此進行優化。

如果在號段消費完之后才從數據庫獲取新的號段,就會存在2個問題:

(1)查詢DB過程出現網絡抖動或慢查詢會導致系統響應時間變長;

(2)DB的I/O操作本身耗時,如果請求進來但號段未取回就會造成阻塞。

雙Buffer

為了能做到DB取號過程能做到無阻塞,當號段消費到某個點時異步吧下一個號段加載到內存中。異步更新,通過雙Buffer的方式,保證在DB服務出現問題時,仍然能夠有一個Buffer號段能夠使用。

詳細實現如下圖:

1630294365846-2153fb4e-1859-4962-9fdd-deb2ff2e0c2b
1630294365846-2153fb4e-1859-4962-9fdd-deb2ff2e0c2b

采用雙buffer的方式,Leaf服務內部有兩個號段緩存區segment。當前號段已下發10%時,如果下一個號段未更新,則另啟一個更新線程去更新下一個號段。當前號段全部下發完后,如果下個號段准備好了則切換到下個號段為當前segment接着下發,循環往復。

解決時鍾問題

因為這種方案依賴時間,如果機器的時鍾發生了回撥,那么就會有可能生成重復的ID號,需要解決時鍾回退的問題。

1630296792707-97a82d32-b19d-430b-b5ee-2e301ca0ee3f
1630296792707-97a82d32-b19d-430b-b5ee-2e301ca0ee3f
1630255764082-f4508b14-9252-425c-8801-0376db42fbf1
1630255764082-f4508b14-9252-425c-8801-0376db42fbf1

參見上圖整個啟動流程圖

(1)服務啟動時首先檢查自己是否寫過ZooKeeper leaf_forever節點:

(2)若寫過,則用自身系統時間與leaf_forever/{self}時間則認為機器時間發生了大步長回撥,服務啟動失敗並報警;

(3)若未寫過,證明是新服務節點,直接創建持久節點leaf_forever/

(4)若abs( 系統時間-sum(time)/nodeSize ) < 閾值,認為當前系統時間准確,正常啟動服務,同時寫臨時節點leaf_temporary/{self} 維持租約;

(5)否則認為本機系統時間發生大步長偏移,啟動失敗並報警;

(6)每隔一段時間(3s)上報自身系統時間寫入leaf_forever/${self};

參考

[1] B+樹簡歷過程(https://www.jianshu.com/p/08fe11a5fbb9)

[2] 美團Leaf源碼分析(https://www.jianshu.com/p/92d34144dcb7)

[3] 美團點評分布式ID生成系統(https://tech.meituan.com/2017/04/21/mt-leaf.html)

[4] 美團分布式ID生成服務開源(https://tech.meituan.com/2019/03/07/open-source-project-leaf.html)

原創文章,轉載請標注。https://www.cnblogs.com/boycelee/p/15227230.html


免責聲明!

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



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