ID生成器之——別人家的方案and自家的方案


“叮咚,叮咚……”,微信提示音一聲接一聲,聲音是那么的頻繁,有妖氣,待俺去看一看。

這天剛吃完午飯,打開微信,發現我們的技術討論組里有 100 多條未讀消息,心想,是不是系統出問題了?怎么消息那么頻繁?

於是迅速的爬樓,歷時 1 秒 23,爬到樓頂,虛驚一場。了解消息的來龍去脈,大體意思:下午兩點,研發一組在第二會議室開會,會議主題是:開發一個適合多個業務場景的分布式 ID 生成器。

到了兩點,我們都來到第二會議室,開始了激烈討論。

可能你們都知道,ID 是某個體系中唯一的編碼,用來標識事務,比如:身份標識號、賬號、唯一編碼、專屬號碼。

ID 生成器又是什么呢?說白了就是生成 ID 工具,而在這里我們說的 ID 生成器,是一個服務(下同)。

為什么要開發分布式 ID 生成器?

原因大體有兩點:

1. 許多業務系統需要對大量的訂單、消息進行進行唯一標識,如:金融、電商、支付等。

2. 每個部門都開發一套 ID 生成器,總體上增加了工作量,增加公司的成本,不利於維護、管理。

分布式 ID 生成器有哪些要求?

1. 全局唯一性:不能出現重復的 ID 號,既然是唯一標識,這是最基本的要求。

2. 遞增:比較低要求的條件為趨勢遞增,即保證下一個 ID 一定大於上一個 ID,而比較苛刻的要求是連續遞增,如1001,1002,1003 等等。根據我們自己的業務,我們選擇的是趨勢遞增(連續遞增,會暴露出系統實際訂單量)。

3. 高可用:ID 生成事關重大,一旦掛掉會導致整個系統崩潰,給公司帶來巨大的損失,需要保證 ID 的正常、穩定生成。

4. 高性能:必須要在壓測下表現良好,如果達不到要求則在高並發環境下依然會導致系統癱瘓。

5. 靈活多變:每個業務場景對 ID 的要求也各不相同,ID 生成要做到靈活多變可配置,盡可能多的滿足需求。

針對以上那么多要求,我們到底要怎么做?看看前輩們都是怎么做的吧,目前業內有幾種常見的解決方案。

 

一、UUID(用的最多)方案

UUID:通用唯一識別碼(Universally Unique Identifier)的標准型式包含 32 個 16 進制數字,以連字號分為五段,形式為 8-4-4-4-12 的 36 個字符,示例:550e8400-e29b-41d4-a716-446655440000。

優點:本地生成,全局唯一,沒有網絡消耗。

缺點:UUID 太長,通常以 36 長度的字符串表示,對 MySQL 索引不利,如果作為數據庫主鍵,在 InnoDB 引擎下,UUID 的無序性可能會引起數據位置頻繁變動,嚴重影響性能;UUID 不能標識業務含義,可讀性差;不滿足遞增要求;不夠靈活。

二、Twitter 的雪花算法 SnowFlake 方案

這種方案大致來說是一種以划分命名空間(UUID 也算,由於比較常見,所以單獨分析)來生成 ID 的一種算法,這種方案把 64-bit 分別划分成多段,分開來標示機器、時間等,比如在 snowflake 中的 64-bit 分別表示如下圖所示:

 

第一位為未使用,接下來的 41 位為毫秒級時間(41 位的長度可以使用 69 年),然后是 5 位 datacenterId 和 5 位 workerId (10 位的長度最多支持部署 1024 個節點) ,最后 12 位是毫秒內的計數(12 位的計數順序號支持每個節點每毫秒產生 4096 個 ID 序號)一共加起來剛好 64 位,為一個 Long 型 (轉換成字符串長度為 18) 。示例:323893460451070160,323893460455264256。

優點:整體上按照時間自增排序;全局唯一;效率高。

缺點:有序,但不連續;不能同時滿足多個系統對ID的需求,不夠靈活。

三、數據庫自增序列生成方案

以 MySQL 舉例,利用給字段設置 auto_increment_increment和 auto_increment_offset 來保證 ID 自增,每次業務使用下列 SQL 讀寫 MySQL 得到 ID 號。

優點:簡單,利用現有數據庫系統的功能實現;成本小,有 DBA 專業維護;ID 號自增,可以實現一些對 ID 有特殊要求的業務。

缺點:強依賴 DB,當 DB 異常時整個系統不可用,屬於致命問題;配置主從復制可以盡可能的增加可用性,但是數據一致性在特殊情況下難以保證;主從切換時的不一致可能會導致重復發號。

ID 發號性能瓶頸限制在單台 MySQL 的讀寫性能。

四、基於 ZooKeeper 和本地緩存的方案

使用 ZooKeeper 作為分段節點協調工具,每台服務器首先從 Zookeeper 緩存一段,如 1 – 2000 的 ID,此時 Zookeeper 上保存最大值 2000,每次獲取的時候都會進行判斷,如果 ID <= 2000,則更新本地的當前值,如果為 2001,則會將 Zookeeper 上的最大值更新至 4000,本地緩存段更新為 2001 - 4000,更新的時候使用分布式鎖來實現。

優點:全局唯一,效率高。

缺點:維護成本較高,不能同時滿足多個系統對 ID 的需求,不夠靈活。

五、我們的方案

看了許多業內解決方案,有些方案已經基本上可以滿足我們的需求,但是不夠靈活。我們需要一種靈活、多變、可配置的方案。

經過一番討論,我們選擇了自己造輪子,核心思想:

使用數據庫雙 buffer 優化方案,每次從數據庫拿取一個號段,當該號段下發 50% 時,如果下一個號段未更新,則啟動另一個線程去提前更新新號段,當該號段已全部下發完成,且下一個號段准備完成,則切換到下一個號段,就這樣一次一次的循環。

舉個栗子:第一次取一個號段 100,000 - 110,000(1),當該號段下發到 105,000 時,去檢查 120,000 號段是否更新,如果未更新,啟動一個線程去更新號段120,000 - 130,000(2),當(1)號段已經下發完成,切換到(2)號段。

這樣做的好處是不用頻繁的訪問數據庫,保證了效率,在數據庫宕機的一段時間內,服務仍然可生成 ID 。

這樣就靈活了嗎?

NO,這提高性能、效率的方式,要想做到靈活,我們需要設計一張數據庫表,表中要區別不同的業務類型,可以設置ID 的前綴規則、后綴規則、長度、步長、最大 ID,保證 ID 靈活、多變、可配置。

數據庫表結構設計:

 

key_name:區分業務

key_length:ID 長度

key_cache:緩存數量

key_prefix:前綴規則

key_subffix:后綴規則

key_digit:key 生成規則,支持 10 進制、36 進制或者 62 進制

 

數據庫表測試數據:

 

測試服務器配置:Linux 2 核 4G 內存 X 2

步長設置為 1000,緩沖池設置為 1000,每秒大約可生成 16,675,231 次。

分布式 ID 生成器的方案還有很多,各有各的優點,需要你們根據自己的業務場景去選擇,“不選貴的,只選對的”。

 

希望這篇文章對你們有幫助,歡迎關注我的公眾號。


免責聲明!

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



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