分佈式ID生成方案(一):總體概述

爲什麼需要分佈式ID

在複雜的業務系統中,往往需要對大量的數據和消息進行唯一標識。如電商系統中常見的訂單號。

系統使用前期,可能數據量小,應用和數據庫方面壓力不大,這樣單體架構的基礎上,我們可以直接使用數據庫自增ID及對應的單號生成規則即可滿足。

但隨着用戶量的增加,數據量也隨之增大,我們會隨之調整爲分佈式系統架構,集羣部署應用、數據庫分庫分表等改造,原來簡單的數據庫自增ID已經不能滿足生成ID的全局唯一性。

分佈式ID特性
  • 全局唯一性:不能出現重複的ID,最基本的要求
  • 趨勢遞增:MySQL InnoDB引擎使用的是聚集索引,由於多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應儘量使用有序的主鍵保證寫入性能。
  • 單調遞增:保證下一個ID一定大於上一個ID。
  • 信息安全:如果ID是連續遞增的,惡意用戶就可以很容易的窺見訂單號的規則,從而猜出下一個訂單號。所以在某些場景下,需要ID無規則。
分佈式ID生成方案

1、UUID:通用唯一識別碼,16個字節128位的長數字

組成部分:
當前時間和時間序列+全局唯一性網卡地址

代碼實現:

public static void main(String[] args) {
      String uuid = UUID.randomUUID().toString().replaceAll("-","");
      System.out.println(uuid);
}

UUID的生成簡單到只有一行代碼,但UUID卻並不適用於實際的業務需求。

像用作訂單號UUID這樣的字符串沒有絲毫的意義,看不出和訂單相關的有用信息;而對於數據庫來說用作業務主鍵ID,它不僅是太長還是字符串,存儲性能差查詢也很耗時,所以不推薦用作分佈式ID。

優點:
代碼實現簡單,不佔用寬帶,數據遷移不受影響

缺點:
無序,無法保證趨勢遞增,查詢慢,不可讀

2、雪花算法:國外的twitter分佈式下ID生成算法

組成部分:
1bit+41bit+10bit+10bit=62bit
高位隨機+毫秒數+機器碼(數據中心+機器id)+10的流水號

優點:
代碼實現簡單,不佔用寬帶,數據遷移不受影響,低位趨勢遞增

缺點:
多臺服務器時間一定要一樣,無序無法保證趨勢遞增要求

3、Mysql

(1)基於數據庫的auto_increment自增ID

當我們需要一個ID的時候,向表中插入一條記錄返回主鍵ID,但這種方式有一個比較致命的缺點,訪問量激增時MySQL本身就是系統的瓶頸,用它來實現分佈式服務風險比較大,不推薦!

優點:
代碼實現方便,性能不錯,數字排序,可讀性很強

缺點:
受限數據庫,擴展麻煩,插入數據庫才能拿到ID,單點故障問題

(2)基於數據庫集羣模式

對方式(1)做高可用優化,換成主從模式集羣,害怕一個主節點掛掉沒法用,那就做雙主模式集羣,也就是兩個Mysql實例都能單獨的生產自增ID。

那這樣還會有個問題,兩個MySQL實例的自增ID都從1開始,會生成重複的ID怎麼辦?

解決方案:
設置起始值和自增步長

優點:
解決DB單點問題

缺點:
不利於後續擴容,而且實際上單個數據庫自身壓力還是大,依舊無法滿足高併發場景。

(3)基於數據庫的號段模式

號段模式是當下分佈式ID生成器的主流實現方式之一,號段模式可以理解爲從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000] 代表1000個ID,具體的業務服務將本號段,生成1~1000的自增ID並加載到內存。

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

4、Redis

Redis的所有命令操作都是單線程的,本身提供像 incr 和 increby 這樣的自增原子命令,所以能保證生成的 ID 肯定是唯一有序的。

優點:
不依賴於數據庫,靈活方便,且性能優於數據庫。
數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:
如果系統中沒有Redis,還需要引入新的組件,增加系統複雜度。
需要佔用網絡資源,性能要比本地生成慢。

用redis實現還需要注意一點,要考慮到redis持久化的問題。redis有兩種持久化方式RDB和AOF:

  • RDB會定時打一個快照進行持久化,假如連續自增但redis沒及時持久化,而這會Redis掛掉了,重啓Redis後會出現ID重複的情況。

  • AOF會對每條寫命令進行持久化,即使Redis掛掉了也不會出現ID重複的情況,但由於incr命令的特殊性,會導致Redis重啓恢復的數據時間過長。

5、百度(uid-generator)

uid-generator是由百度技術部開發,項目GitHub地址 https://github.com/baidu/uid-generator

uid-generator是基於Snowflake算法實現的,與原始的snowflake算法不同在於,uid-generator支持自定義時間戳、工作機器ID和 序列號 等各部分的位數,而且uid-generator中採用用戶自定義workId的生成策略。

uid-generator需要與數據庫配合使用,需要新增一個WORKER_NODE表。當應用啓動時會向數據庫表中去插入一條數據,插入成功後返回的自增ID就是該機器的workId數據由host,port組成。

6、美團(Leaf)

Leaf由美團開發,github地址:https://github.com/Meituan-Dianping/Leaf

Leaf同時支持號段模式和snowflake算法模式,可以切換使用。

7、滴滴(Tinyid)

Tinyid由滴滴開發,Github地址:https://github.com/didi/tinyid。

Tinyid是基於號段模式原理實現的與Leaf如出一轍,每個服務獲取一個號段(1000,2000]、(2000,3000]、(3000,4000]

總結

以上基本列出了所有常用的分佈式ID生成方式,其實大致分類的話可以分爲兩類:

一種是類DB型的,根據設置不同起始值和步長來實現趨勢遞增,需要考慮服務的容錯性和可用性。

另一種是類snowflake型,這種就是將64位劃分爲不同的段,每段代表不同的涵義,基本就是時間戳、機器ID和序列數。

每種生成方式都有它自己的優缺點,具體如何使用還要看具體的業務需求。

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