Mysql面試備用,四種方式生成分佈式全局唯一ID


前言

在複雜的分佈式系統中,需要對大量的數據和消息進行唯一的標識。比如美團的金融、支付、餐飲、酒店、貓眼電影等產品的系統中,數據日益增長,將數據庫分表後需要一個唯一的ID來標示一條數據或消息,數據庫的自增ID顯然不能滿足需求,特別是訂單、騎手、優惠券也需要有唯一的ID才能識別。

此時,一個能夠生成全局唯一ID的系統是非常必要的。


一、為什麼要用分佈式ID?


在討論分佈式ID的具體實現之前,我們先簡要分析一下為什麼要使用分佈式ID?分佈式ID應該滿足哪些特徵?


1、分佈式ID是什麼?

當我們的業務數據量不大時,一個數據庫和一張表就可以完全支持現有的業務。對較多的數據採用MySQL主從同步讀寫分離的方案也可以應付。


但是,隨著數據的不斷增長,主從同步無法實現,因此有必要將數據庫分庫分表。但是分庫分表後,需要一個唯一的ID來標識一條數據,而數據庫的自增ID顯然不能滿足需求。特別點的例如訂單和優惠券需要有一個唯一的ID來標示。此時,一個能夠生成全局唯一ID的系統是非常必要的那麼這個全局唯一ID就叫分佈式ID。


2、分佈式ID有什麼要求?


Mysql面試備用,四種方式生成分佈式全局唯一ID

2、 分佈式ID的生成方法有哪些?


先看最簡單的六種模式。


Mysql面試備用,四種方式生成分佈式全局唯一ID

我們在這章節內容會討論它們的實現方式,以及各種優缺點。

1、基於UUID

UUID(Universally Unique Identifier)全局唯一標識符,是指在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。按照開放軟件基金會(OSF)制定的標準計算,用到了以太網卡地址、納秒級時間、芯片ID碼和許多可能的數字。由以下幾部分的組合:當前日期和時間(UUID的第一個部分與時間有關,如果你在生成一個UUID之後,過幾秒又生成一個UUID,則第一個部分不同,其餘相同),時鐘序列,全局唯一的IEEE機器識別號(如果有網卡,從網卡獲得,沒有網卡以其他方式獲得),UUID的唯一缺陷在於生成的結果串會比較長。

看一下java版本如何獲取UUID。

我們可以使用java下的UUID類,用它可以產生一個號稱全球唯一的ID。

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

輸出結果 c2b8c2b9e46c47e3b30dca3b0d447718。

雖然使用簡單但是缺點也很明顯,比較佔地方,和INT類型相比,影響插入速度, 並且造成硬盤使用率低存儲一個UUID要花費更多的空間,使用UUID後,URL顯得冗長,不夠友好,像用作訂單號UUID這樣的字符串沒有絲毫的意義,看不出和訂單相關的有用信息。所以不推薦用做主鍵。

好處是出現數據拆分、合併存儲的時候,能達到全局的唯一性,生成也比較簡單。


2、基於數據庫自增ID

基於數據庫的auto_increment自動增量ID可以作為分佈式ID,具體實現:需要單獨的MySQL實例生成ID,表結構如下:


Mysql面試備用,四種方式生成分佈式全局唯一ID

當我們需要一個ID時,在表中插入一條記錄以返回主鍵ID。但是暴露出的缺點還是蠻大的,當訪問量急劇增加時,MySQL容易達到系統的瓶頸。不建議使用它來實現分佈式服務!

優點:

  • 數據庫自動編號,速度快,而且是增量增長,按順序存放,對於檢索非常有利;。

缺點:

  • DB單點存在宕機風險。很難處理分佈式存儲的數據表,數據量特別大時,會導致查詢數據庫操作變慢。

3、基於數據庫集群模式

前邊說了單點數據庫方式不可取,那對上邊的方式做一些高可用優化,換成主從模式集群。害怕一個主節點掛掉沒法用,那就做雙主模式集群,也就是兩個Mysql實例都能單獨的生產自增ID。

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

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


Mysql面試備用,四種方式生成分佈式全局唯一ID

兩個MySQL實例的生成的自增ID為:

1、3、5、7、9


2、4、6、8、10

如果使用集群后的性能仍然頂不住高併發怎麼辦?那麼就要對Mysql的節點進行擴容。


Mysql面試備用,四種方式生成分佈式全局唯一ID

從上圖可以看出,水平擴展的數據庫集群有利於解決數據庫單點壓力問題。同時,對於ID生成特性,根據機器數量設置自動遞增步長。

要增加第三臺MySQL實例時,需要手動修改第一臺和第二臺的MySQL實例的起始值和步長,把第三臺機器的ID起始生成位置設定在比現有最大自增ID的位置遠一些,但必須在一、二兩臺MySQL實例ID還沒有增長到第三臺MySQL實例的起始ID值的時候,否則自增ID就要出現重複了,

必要時可能還需要停機修改


Mysql面試備用,四種方式生成分佈式全局唯一ID

4,雪花模式

基於twitter snowflake算法。

雪花模式基本思路是,將一個64位的long分割為3部分,使用時間遞增的特性,來生成唯一的值。


Mysql面試備用,四種方式生成分佈式全局唯一ID

其中timestamp為當前毫秒數減去某個固定值(當前固定值取2018.01.01時的毫秒值),workerId佔10位,因此可以容納最多1024個服務實例同時使用。 sequence佔12位,是單毫秒內遞增值,也就是1毫秒內可以最多4096個值,意味著可以容納的單個服務實例的最大QPS為4096000。

雪花模式非常依賴服務器時間,所以它的最大問題/隱患在於時間回撥,如果服務器時間回撥,那麼就會生成重複的ID。 比如閏秒時的時間回撥,或者由於某些原因管理員修改了系統時間。 雖然在程序中判斷了時間需要遞增,但是如果在服務重啟過程中發生了時間回撥,則無法避免產生相同的ID。

如果服務對錯誤容忍度高且希望簡單方便,推薦使用雪花模式。

其實除了這4種外還有其它好用的中間件能為我們生成分佈式全局ID。感興趣的可以在網上了解下其它的方法。



分享到:


相關文章: