分布式ID生成方案的深度解析與Java實現

在分布式系統中,生成全局唯一的ID是一項核心需求,廣泛應用于訂單編號、用戶信息、日志追蹤等場景。分布式ID不僅需要保證全局唯一性,還要滿足高性能、高可用性以及一定的可讀性要求。本文將深入探討分布式ID的概念、設計要點、常見生成方案,并通過Java代碼實現幾種典型方案,旨在幫助開發者理解分布式ID的技術本質,并提供實踐參考。


一、分布式ID的背景與挑戰

分布式系統由多個節點組成,節點間通常通過網絡通信,缺乏全局時鐘或統一協調機制。傳統的單機環境下,自增ID(如數據庫主鍵)可以輕松滿足唯一性需求,但在分布式環境中,節點獨立運行,簡單地依賴數據庫自增ID可能導致沖突或性能瓶頸。因此,分布式ID需要解決以下核心問題:

  1. 全局唯一性:在所有節點生成的ID必須全局唯一,不能出現重復。
  2. 高性能:生成ID的速度要快,通常要求毫秒級甚至微秒級響應。
  3. 高可用性:ID生成服務需保證24/7可用,單點故障不能影響整體功能。
  4. 有序性:某些場景(如日志排序)要求ID具有時間單調遞增或趨勢遞增的特性。
  5. 可讀性:ID可能需要包含業務信息(如時間、地域),便于調試或分析。
  6. 擴展性:系統規模擴大時,ID生成方案需支持水平擴展。

這些要求使得分布式ID生成成為分布式系統設計中的一個復雜問題。以下我們將分析幾種主流方案,探討其優缺點,并提供Java實現。


二、分布式ID的常見生成方案

分布式ID生成方案可以分為以下幾類,每類方案在不同場景下有其適用性:

1. 數據庫自增ID

利用關系型數據庫(如MySQL)的自增主鍵生成ID,簡單易用,但在分布式場景下性能受限。

  • 優點:實現簡單,ID單調遞增,易于理解。
  • 缺點:數據庫寫入成為性能瓶頸,高并發下可能導致鎖競爭;擴展性差,依賴數據庫可用性。
  • 適用場景:低并發、對性能要求不高的業務。

2. UUID

UUID(Universally Unique Identifier)是基于隨機數或時間戳生成的128位標識符。

  • 優點:完全去中心化,生成無需協調,沖突概率極低。
  • 缺點:長度過長(36字符),存儲和傳輸成本高;無序性導致數據庫索引性能下降。
  • 適用場景:對唯一性要求高但對性能和可讀性要求低的場景。

3. 基于時間戳的Snowflake算法

Snowflake算法由Twitter提出,是一種基于時間戳的分布式ID生成方案,ID為64位整數,結構通常包括:

  • 時間戳:表示ID生成的時間,占41位(支持約69年)。

  • 機器ID:標識生成節點,占10位(支持1024個節點)。

  • 序列號:同一毫秒內的計數器,占12位(每毫秒支持4096個ID)。

  • 符號位:占1位,通常為0。

  • 優點:高性能,ID趨勢遞增,支持高并發,結構清晰。

  • 缺點:依賴系統時鐘,時間回撥可能導致ID沖突;機器ID需手動分配。

  • 適用場景:高并發、需要趨勢遞增ID的業務,如訂單系統。

4. 數據庫分段(Leaf-Segment)

由美團提出的Leaf方案,通過數據庫預分配ID段(如1000個ID),節點從內存中獲取ID,耗盡后再從數據庫申請新段。

  • 優點:簡單可靠,支持批量獲取,減少數據庫壓力。
  • 缺點:數據庫仍是潛在瓶頸,需處理段分配的并發問題。
  • 適用場景:對性能要求適中、希望簡單實現的場景。

5. 分布式協調服務(如ZooKeeper)

使用ZooKeeper等分布式協調服務生成遞增ID,基于其順序節點特性。

  • 優點:強一致性,ID嚴格遞增。
  • 缺點:性能較低,依賴外部服務,增加了系統復雜性。
  • 適用場景:對一致性要求極高的場景,如金融系統。

6. Redis生成ID

利用Redis的原子遞增操作(如INCR命令)生成ID。

  • 優點:高性能,簡單易用。
  • 缺點:依賴Redis可用性,持久化可能導致ID丟失;ID無業務含義。
  • 適用場景:高并發、對可讀性要求低的場景。

三、分布式ID的設計要點

在選擇或設計分布式ID生成方案時,需考慮以下關鍵因素:

  1. 時鐘依賴:基于時間戳的方案(如Snowflake)需處理時鐘回撥問題,可通過拒絕生成或等待解決。
  2. ID長度:ID長度影響存儲效率,64位整數是常見選擇,兼容大多數數據庫和系統。
  3. 分區策略:機器ID或業務ID的分配需避免沖突,可通過配置中心或數據庫管理。
  4. 容錯性:生成服務需支持故障轉移,主備切換或多節點負載均衡。
  5. 可擴展性:方案需適應節點增加,動態分配ID空間。
  6. 業務定制:某些場景要求ID嵌入業務信息,如區域、業務類型等。

四、Java實現:Snowflake算法與Leaf-Segment方案

下面我們通過Java代碼實現兩種典型的分布式ID生成方案:Snowflake算法和Leaf-Segment方案,并附上詳細注釋和使用示例。

1. Snowflake算法實現

Snowflake算法因其高性能和趨勢遞增特性,成為許多分布式系統的首選。以下是一個線程安全的Java實現,支持時間回撥處理。

public class SnowflakeIdGenerator {// 起始時間戳(2023-01-01 00:00:00)private static final long START_TIMESTAMP = 1672502400000L;// 各部分位數private static final long WORKER_ID_BITS = 10L; // 機器ID占10位private static final long SEQUENCE_BITS = 12L;  // 序列號占12位// 最大值private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 1023private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);   // 4095// 位移量private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;// 內部狀態private long workerId;private long sequence = 0L;private long lastTimestamp = -1L;public SnowflakeIdGenerator(long workerId) {if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);}this.workerId = workerId;}public synchronized long nextId() {long timestamp = System.currentTimeMillis();// 處理時間回撥if (timestamp < lastTimestamp) {throw new RuntimeException("Clock moved backwards. Refusing to generate ID.");}// 同一毫秒內,增加序列號if (lastTimestamp == timestamp) {sequence = (sequence + 1) & MAX_SEQUENCE;if (sequence == 0) {// 序列號溢出,等待下一毫秒timestamp = waitNextMillis(lastTimestamp);}} else {sequence = 0L; // 新毫秒,重置序列號}lastTimestamp = timestamp;// 組裝IDreturn ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)| (workerId << WORKER_ID_SHIFT)| sequence;}private long waitNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}public static void main(String[] args) {SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1);for (int i = 0; i < 10; i++) {System.out.println(idGenerator.nextId());}}
}

代碼說明

  • 結構:ID由41位時間戳(支持約69年)、10位機器ID(支持1024個節點)、12位序列號(每毫秒4096個ID)組成。
  • 時間回撥:通過拋出異常拒絕生成,實際生產中可改為等待或使用緩存時間。
  • 線程安全:使用 synchronized 確保并發安全,適用于中等并發場景。
  • 使用示例:運行 main 方法將生成10個唯一ID,輸出類似 1234567890123 的64位整數。

優化建議

  • 高并發:可引入線程池或異步生成,提升吞吐量。
  • 機器ID分配:通過ZooKeeper或數據庫動態分配workerId。
  • 時間回撥改進:維護一個時間緩存,或在回撥時借用序列號空間。

2. Leaf-Segment方案實現

Leaf-Segment方案通過數據庫預分配ID段,節點從內存獲取ID,適合簡單可靠的場景。以下是Java實現,假設使用MySQL存儲ID段。

首先,創建數據庫表:

CREATE TABLE id_segment (biz_tag VARCHAR(50) PRIMARY KEY COMMENT '業務標簽',max_id BIGINT NOT NULL DEFAULT 0 COMMENT '當前最大ID',step INT NOT NULL DEFAULT 1000 COMMENT '步長',update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO id_segment (biz_tag, max_id, step) VALUES ('order', 0, 1000);

Java實現:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class LeafSegmentIdGenerator {private String bizTag;private String jdbcUrl = "jdbc:mysql://localhost:3306/test?useSSL=false";private String username = "root";private String password = "password";private volatile long currentId;private volatile long maxId;private final int step;public LeafSegmentIdGenerator(String bizTag) {this.bizTag = bizTag;this.step = 1000; // 默認步長loadSegment(); // 初始化ID段}public synchronized long nextId() {if (currentId >= maxId) {loadSegment(); // ID段耗盡,重新加載}return currentId++;}private void loadSegment() {try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {conn.setAutoCommit(false);// 獲取當前最大ID并更新String updateSql = "UPDATE id_segment SET max_id = max_id + step WHERE biz_tag = ?";PreparedStatement updateStmt = conn.prepareStatement(updateSql);updateStmt.setString(1, bizTag);int rows = updateStmt.executeUpdate();if (rows == 0) {throw new RuntimeException("Failed to update ID segment for " + bizTag);}// 查詢新ID段String selectSql = "SELECT max_id FROM id_segment WHERE biz_tag = ?";PreparedStatement selectStmt = conn.prepareStatement(selectSql);selectStmt.setString(1, bizTag);ResultSet rs = selectStmt.executeQuery();if (rs.next()) {maxId = rs.getLong("max_id");currentId = maxId - step + 1;} else {throw new RuntimeException("No segment found for " + bizTag);}conn.commit();} catch (Exception e) {throw new RuntimeException("Failed to load ID segment", e);}}public static void main(String[] args) {LeafSegmentIdGenerator idGenerator = new LeafSegmentIdGenerator("order");for (int i = 0; i < 10; i++) {System.out.println(idGenerator.nextId());}}
}

代碼說明

  • 邏輯:節點從數據庫獲取一個ID段(如1001-2000),在內存中遞增生成ID,耗盡后再申請新段。
  • 數據庫交互:使用樂觀鎖(UPDATE直接修改)確保并發安全,事務保證數據一致性。
  • 業務隔離:通過 biz_tag 支持多業務隔離,如“order”和“user”可獨立分配ID。
  • 使用示例:運行 main 方法將生成連續的ID,如 1001, 1002, ...

優化建議

  • 批量獲取:增加步長(如10000),減少數據庫訪問。
  • 雙緩沖:異步加載下一段ID,避免生成延遲。
  • 高可用:引入主備數據庫或緩存(如Redis)提高可靠性。

五、各方案對比與選擇

以下是對上述方案的對比總結:

方案唯一性性能有序性可讀性擴展性依賴性適用場景
數據庫自增ID數據庫低并發簡單業務
UUID對性能敏感、無序性可接受
Snowflake趨勢時鐘高并發、需要趨勢遞增
Leaf-Segment數據庫中等并發、簡單實現
ZooKeeperZooKeeper強一致性需求
RedisRedis高并發、無可讀性要求

選擇建議

  • 高并發場景:Snowflake或Redis,性能優異,適合訂單、日志等系統。
  • 簡單實現:Leaf-Segment,易于部署,適合中小規模業務。
  • 強一致性:ZooKeeper,適用于金融等對ID順序敏感的場景。
  • 無序可接受:UUID,適合快速開發或臨時場景。

六、分布式ID的未來趨勢

隨著分布式系統規模的擴大,ID生成方案也在不斷演進。以下是一些值得關注的趨勢:

  1. 云原生集成:云服務(如AWS、阿里云)提供托管ID生成服務,降低開發成本。
  2. 多租戶支持:ID方案需支持多租戶隔離,嵌入租戶標識。
  3. AI優化:通過機器學習預測ID需求,優化分配策略。
  4. 去中心化趨勢:基于區塊鏈或P2P網絡生成ID,減少對中心化服務的依賴。

七、實踐中的注意事項

  1. 測試覆蓋:對ID生成方案進行并發測試,確保唯一性和性能。
  2. 監控告警:監控ID生成速率、時間回撥等異常情況,及時干預。
  3. 文檔化:記錄ID結構(如Snowflake的位分配),便于維護和調試。
  4. 回滾策略:為ID生成服務設計降級方案,如切換到備用算法。
  5. 合規性:在涉及用戶數據的場景中,確保ID不泄露敏感信息。

八、總結

分布式ID生成是分布式系統中的核心技術之一,其設計需要在唯一性、性能、可用性和可讀性之間找到平衡。本文詳細分析了數據庫自增ID、UUID、Snowflake、Leaf-Segment、ZooKeeper和Redis等方案的優缺點,并通過Java代碼實現了Snowflake和Leaf-Segment兩種主流方案。實踐表明,Snowflake因其高性能和趨勢遞增特性成為許多高并發場景的首選,而Leaf-Segment則以簡單可靠著稱。開發者應根據業務需求選擇合適的方案,并結合監控和優化確保系統穩定運行。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901035.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901035.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901035.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

記 etcd 無法在docker-compose.yml啟動后無法映射數據庫目錄的問題

1、將etcd 單獨提取 Dockerfile&#xff0c;指定配置文件和數據目錄 #鏡像 FROM bitnami/etcd:3.5.11 #名稱 ENV name"etcd" #重啟 ENV restart"always" #運行無權限 ENV ALLOW_NONE_AUTHENTICATION"yes" #端口 EXPOSE 2379 2380 #管理員權限才…

怎樣才不算干擾球·棒球1號位

在棒球運動中&#xff0c;"干擾球"&#xff08;Interference&#xff09;是指球員或場外人員非法影響了比賽的正常進行。以下情況通常 不構成干擾&#xff0c;屬于合法行為或無需判罰&#xff1a; 1. 擊跑員&#xff08;Batter-Runner&#xff09;合法跑壘 跑壘限制…

PyTorch實現多輸入輸出通道的卷積操作

本文通過代碼示例詳細講解如何在PyTorch中實現多輸入通道和多輸出通道的卷積運算&#xff0c;并對比傳統卷積與1x1卷積的實現差異。 1. 多輸入通道互相關運算 當輸入包含多個通道時&#xff0c;卷積核需要對每個通道分別進行互相關運算&#xff0c;最后將結果相加。以下是實現…

深入解析 MySQL 中的日期時間函數:DATE_FORMAT 與時間查詢優化、DATE_ADD、CONCAT

深入解析 MySQL 中的日期時間函數&#xff1a;DATE_FORMAT 與時間查詢優化 在數據庫管理和應用開發中&#xff0c;日期和時間的處理是不可或缺的一部分。MySQL 提供了多種日期和時間函數來滿足不同的需求&#xff0c;其中DATE_FORMAT函數以其強大的日期格式化能力&#xff0c;…

SSH配置優化:提升本地內網Linux服務器遠程連接速度與穩定性

文章目錄 引言一. 理解SSH連接過程與影響因素二. 服務器端SSH配置優化三. 客戶端SSH配置優化四. 高級技巧五. 內網穿透突破公網IP限制總結 引言 SSH (Secure Shell) 是一種網絡協議&#xff0c;用于加密的網絡服務&#xff0c;常用于遠程登錄和管理Linux服務器。對于本地內網的…

BERT - MLM 和 NSP

本節代碼將實現BERT模型的兩個主要預訓練任務&#xff1a;掩碼語言模型&#xff08;Masked Language Model, MLM&#xff09; 和 下一句預測&#xff08;Next Sentence Prediction, NSP&#xff09;。 1. create_nsp_dataset 函數 這個函數用于生成NSP任務的數據集。 def cr…

“實時滾動”插件:一個簡單的基于vue.js的無縫滾動

1、參考連接&#xff1a; 安裝 | vue-seamless-scroll 2、使用步驟&#xff1a; 第一步&#xff1a;安裝 yarn add vue-seamless-scroll 第二步&#xff1a;引入 import vueSeamlessScroll from vue-seamless-scroll/src 第三步&#xff1a;注冊 components: { vueSeamless…

【藍橋杯】賽前練習

1. 排序 import os import sysn=int(input()) data=list(map(int,input().split(" "))) data.sort() for d in data:print(d,end=" ") print() for d in data[::-1]:print(d,end=" ")2. 走迷宮BFS import os import sys from collections import…

pyTorch-遷移學習-學習率衰減-四種天氣圖片多分類問題

目錄 1.導包 2.加載數據、拼接訓練、測試數據的文件夾路徑 3.數據預處理 3.1 transforms.Compose數據轉化 3.2分類存儲的圖片數據創建dataloader torchvision.datasets.ImageFolder torch.utils.data.DataLoader 4.加載預訓練好的模型(遷移學習) 4.1固定、修改預訓練…

第十四屆藍橋杯大賽軟件賽國賽Python大學B組題解

文章目錄 彈珠堆放劃分偶串交易賬本背包問題翻轉最大階梯最長回文前后綴貿易航線困局 彈珠堆放 遞推式 a i a i ? 1 i a_ia_{i-1}i ai?ai?1?i&#xff0c; n 20230610 n20230610 n20230610非常小&#xff0c;直接模擬 答案等于 494 494 494 劃分 因為總和為 1 e 6 1e6…

Python 和 JavaScript兩種語言的相似部分-由DeepSeek產生

Python 和 JavaScript 作為兩種流行的編程語言&#xff0c;雖然在設計目標和應用場景上有差異&#xff08;Python 偏向后端和腳本&#xff0c;JavaScript 偏向前端和動態交互&#xff09;&#xff0c;但它們的語法存在許多相似之處。以下是兩者在語法上的主要共同點及對比&…

改善 Maven 的依賴性

大家好&#xff0c;這里是架構資源棧&#xff01;點擊上方關注&#xff0c;添加“星標”&#xff0c;一起學習大廠前沿架構&#xff01; 建議使用mvn dependency:analyze命令來擺脫已聲明但未使用的依賴項&#xff1a; 還有另一個用例&#xff0c; mvn dependency:analyze 它可…

【SQL】子查詢詳解(附例題)

子查詢 子查詢的表示形式為&#xff1a;(SELECT 語句)&#xff0c;它是IN、EXISTS等運算符的運算數&#xff0c;它也出現于FROM子句和VALUES子句。包含子查詢的查詢叫做嵌套查詢。嵌套查詢分為相關嵌套查詢和不想關嵌套查詢 WHERE子句中的子查詢 比較運算符 子查詢的結果是…

Stable Diffusion 擴展知識實操整合

本文的例子都是基于秋葉整合包打開的webui實現的 一、ADetailer——改善人臉扭曲、惡心 After detailer插件可以自動檢測生成圖片的人臉&#xff0c;針對人臉自動上蒙版&#xff0c;自動進行重繪&#xff0c;整個流程一氣呵成&#xff0c;因此可以避免許多重復的操作。除此之…

freertos內存管理簡要概述

概述 內存管理的重要性 在嵌入式系統中&#xff0c;內存資源通常是有限的。合理的內存管理可以確保系統高效、穩定地運行&#xff0c;避免因內存泄漏、碎片化等問題導致系統崩潰或性能下降。FreeRTOS 的內存管理機制有助于開發者靈活地分配和釋放內存&#xff0c;提高內存利用…

按規則批量修改文件擴展名、刪除擴展名或添加擴展名

文件的擴展名是多種多樣的&#xff0c;有些不同文件的擴展名之間相互是可以直接轉換的。我們工作當中最常見的就是 doc 與 docx、xls 與 xlsx、jpg 與 jpeg、html 與 htm 等等&#xff0c;這些格式在大部分場景下都是可以相互轉換 能直接兼容的。我們今天要介紹的就是如何按照一…

熱門面試題第15天|最大二叉樹 合并二叉樹 驗證二叉搜索樹 二叉搜索樹中的搜索

654.最大二叉樹 力扣題目地址(opens new window) 給定一個不含重復元素的整數數組。一個以此數組構建的最大二叉樹定義如下&#xff1a; 二叉樹的根是數組中的最大元素。左子樹是通過數組中最大值左邊部分構造出的最大二叉樹。右子樹是通過數組中最大值右邊部分構造出的最大…

MySQL學習筆記7【InnoDB】

Innodb 1. 架構 1.1 內存部分 buffer pool 緩沖池是主存中的第一個區域&#xff0c;里面可以緩存磁盤上經常操作的真實數據&#xff0c;在執行增刪查改操作時&#xff0c;先操作緩沖池中的數據&#xff0c;然后以一定頻率刷新到磁盤&#xff0c;這樣操作明顯提升了速度。 …

RNN、LSTM、GRU匯總

RNN、LSTM、GRU匯總 0、論文匯總1.RNN論文2、LSTM論文3、GRU4、其他匯總 1、發展史2、配置和架構1.配置2.架構 3、基本結構1.神經元2.RNN1. **RNN和前饋網絡區別&#xff1a;**2. 計算公式&#xff1a;3. **梯度消失:**4. **RNN類型**:&#xff08;查看發展史&#xff09;5. **…

django數據遷移操作受阻

錯誤信息&#xff1a; django.db.utils.OperationalError: (1227, Access denied; you need (at least one of) the SYSTEM_VARIABLES_ADMIN or SESSION_VARIABLES_ADMIN privilege(s) for this operation)根據錯誤信息分析&#xff0c;該問題是由于MySQL用戶 缺乏SYSTEM_VARI…