如何在分佈式場景下生成全局惟一 ID ?

在分佈式系統中,有一些場景須要使用全局惟一 ID ,能夠和業務場景有關,好比支付流水號,也能夠和業務場景無關,好比分庫分表後須要有一個全局惟一 ID,或者用做事務版本號、分佈式鏈路追蹤等等,好的全局惟一 ID 須要具有這些特色:node

  • 全局惟一:這是最基本的要求,不能重複;
  • 遞增:有些特殊場景是必須遞增的,好比事務版本號,後面生成的 ID 必定要大於前面的 ID ;有些場景遞增比不遞增要好,由於遞增有利於數據庫索引的性能;
  • 高可用:若是是生成惟一 ID 的系統或服務,那麼必定會有大量的調用,那麼保證其高可用就很是關鍵了;
  • 信息安全:若是 ID 是連續的,那麼很容易被惡意操做或泄密,好比訂單號是連續的,那麼很容易就被看出來一天的單量大概是多少;
  • 另外考慮到存儲壓力,ID 固然是越短越好。

那麼分佈式場景下有哪些生成惟一 ID 的方案呢?算法

利用數據庫生成

先說最容易理解的方案,利用數據庫的自增加序列生成:數據庫生成惟一主鍵,並經過服務提供給其餘系統;若是是小型系統,數據總量和併發量都不是很大的狀況下,這種方案足夠支撐。數據庫

若是每次生成一個 ID 可能會對數據庫有壓力,能夠考慮一次性生成 N 個 ID 放入緩存中,若是緩存中的 ID 被取光,再經過數據庫生成下一批 ID 。緩存

  • 優勢: 理解起來最容易,實現起來也最簡單。
  • 缺點: 也很是明顯了,每種數據庫的實現不一樣,若是數據庫須要遷移的話比較麻煩;最大的問題是性能問題,併發量到必定級別的時候這個方法估計會很難知足性能需求;另外經過數據庫自增生成的 ID 攜帶的信息太少,只能起到一個標識的做用,同時自增 ID 也是連續的。

利用其餘組件/軟件/中間件生成

利用 Redis / MongoDB / zookeeper 生成:Redis 利用 incr 和 increby ;MongoDB 的 ObjectId;zk 經過 znode 數據版本;均可以生成全局的惟一標識碼。安全

咱們用 MongoDB 的 ObjectId 來舉例:網絡

{"_id": ObjectId("5d47ca7528021724ac19f745")}

MongoDB 的 ObjectId 共佔 12 個字節,其中:架構

  • 3.2 以前的版本(包括 3.2): 4 字節時間戳 + 3 字節機器標識符 + 2 字節進程 ID + 3字節隨機計數器
  • 3.2 以後版本: 4 字節時間戳 + 5 字節隨機值 + 3 字節遞增計數器

不論是老版本仍是新版本,MongoDB 的 ObjectId 至少均可以保證集羣內的惟一,咱們能夠搭建一個全局惟一 ID 生成的服務,利用 MongoDB 生成 ObjectId 並對外提供服務(MongoDB 的各語言驅動都實現了 ObjectId 的生成算法)。併發

  • 優勢: 性能高於數據庫;可使用集羣部署;ID 內自帶一些含義,好比時間戳;
  • 缺點: 和數據庫同樣,須要引入對應的組件/軟件,增長了系統的複雜度;最關鍵的是,這兩種方案都意味着生成全局惟一 ID 的系統(服務),會成爲一個單點,在軟件架構中,單獨就意味着風險;若是這個服務出現問題,那麼全部依賴於這個服務的系統都會崩潰掉。

UUID

這個是分佈式架構中,生成惟一標識碼最經常使用的算法。爲了保證 UUID 的惟一性,生成因素包括了MAC地址、時間戳、名字空間(Namespace)、隨機或僞隨機數、時序等元素;UUID 有多個版本,每一個版本的算法不一樣,應用範圍也不一樣:框架

  • Version 1: 基於時間的 UUID,是經過時間戳 + 隨機數 + MAC地址獲得;若是應用直接局域網內使用,可使用 IP 地址替代 MAC 地址;高度惟一(MAC 地址泄漏,也是一個安全問題)。
  • Version 2: DCE 安全的 UUID,把 Version 1 中的時間戳前 4 位置換爲 POSIX 的 UID 或 GID ;高度惟一。
  • Version 3: 基於名字的 UUID(MD5),經過計算名字和名字空間的 MD5 散列值獲得;必定範圍內惟一。
  • Version 4: 隨機 UUID,根據隨機數或僞隨機數生成 UUID;有必定機率重複。
  • Version 5: 基於名字的UUID(SHA1),和 Version 3 相似,只是散列值計算使用SHA1算法;必定範圍內惟一。
public class CreateUUID {
 public static void main(String[] args) {
  String uuid = UUID.randomUUID().toString();
  System.out.println("uuid : " + uuid);
​
  uuid = UUID.randomUUID().toString().replaceAll("-","");
  System.out.println("uuid : " + uuid);
 }
}
  • 優勢: 本地生成,沒有網絡消耗,不須要第三方組件(也就沒有單點的風險),生成比較簡單,性能好。
  • 缺點: 長度長,不利於存儲,而且沒有排序,相對來講還會影響性能(好比 MySQL 的 InnoDB 引擎,若是 UUID 做爲數據庫主鍵,其無序性會致使數據位置頻繁變更)。

Snowflake

若是但願 ID 能夠本地生成,可是又不要和 UUID 那樣無序,能夠考慮使用 Snowflake 算法(Twitter開源)。dom

SnowFlake 算法生成 ID 是一個 64 bit 的整數,包括:

  • 1 bit : 不使用,固定是 0 ;
  • 41 bit : 時間戳(毫秒),數值範圍是:0 至 2的41次方 - 1 ;轉換成年的話,大約是 69 年;
  • 10 bit : 機器 ID ;5 位機房 ID + 5 位機器 ID ;(服務集羣數量比較小的時候,能夠手動配置,服務規模大的話,能夠採用第三方組件進行自動配置,好比美團的 Leaf-snowflake,就是經過 Zookeeper 的持久順序節點作爲機器 ID)
  • 12 bit : 序列號,用來記錄同一個毫秒內生成的不一樣 ID 。

在Java中,SnowFlake 算法生成的 ID 正好能夠用 long 來進行存儲。

  • 優勢: 本地生成,沒有網絡消耗,不須要第三方組件(也就沒有單點的風險),必定範圍內惟一(基本能夠知足大部分場景),性能好,按時間戳遞增(趨勢遞增);
  • 缺點: 依賴於機器時鐘,同一臺機器若是把時間回撥,生成的 ID 就會有重複的風險。

image

此外,還有不少優秀的互聯網公司也提供了惟一 ID 生成的方案或框架,好比美團開源的 Leaf ,百度開源的 UidGenerator 等等。

@Resource
private UidGenerator uidGenerator;
​
@Test
public void testSerialGenerate() {
    // Generate UID
    long uid = uidGenerator.getUID();
    System.out.println(uidGenerator.parseUID(uid));
}
會點代碼的大叔 | 文【原創】

敬請關注會點代碼的大叔

相關文章
相關標籤/搜索