1. 程式人生 > 實用技巧 >springboot 全域性異常處理器

springboot 全域性異常處理器

1. 需求分析

在複雜分散式系統中,往往需要對大量的資料和訊息進行唯一標識。如在電商、金融、支付等系統中,資料日漸增長,對資料分庫分表後需要有一個唯一ID來標識一條資料或訊息,資料庫的自增ID不能滿足需求,此時一個能夠生成全域性唯一ID的系統是非常必要的。概括下來,那業務系統對ID號的要求有哪些呢?

  1. 全域性唯一性:不能出現重複的ID號,既然是唯一標識,這是最基本的要求。
  2. 趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由於多數RDBMS使用B-tree的資料結構來儲存索引資料,在主鍵的選擇上面我們應該儘量使用有序的主鍵保證寫入效能。
  3. 單調遞增:保證下一個ID一定大於上一個ID,例如事務版本號、IM增量訊息、排序等特殊需求。
  4. 資訊保安:如果ID是連續的,惡意使用者的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道系統一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。

上述123對應三類不同的場景,3和4需求還是互斥的,無法使用同一個方案滿足。

同時除了對ID號碼自身的要求,業務還對ID號生成系統的可用性要求極高,想象一下,如果ID生成系統癱瘓,整個業務系統都無法執行。

由此總結下一個ID生成系統應該做到如下幾點:

  1. 平均延遲和TP999延遲都要儘可能低;
  2. 可用性5個9;
  3. 高QPS。

2. 為什麼資料庫自增ID無法滿足需求

如果是資料庫的自增ID的話,那麼ID自增是在單庫單表之中的,如果做了分庫分表之後要使用全域性的自增ID,就需要建立全域性唯一的Sequence表(類似Hive MetaStore之中的sequence_table表)來表示全域性自增的ID值。

  • 效能問題
    此時每個分資料庫中需要插入資料,都得先競爭請求Sequence表來確定下一個自增ID,而且請求更新時一定是要加鎖的
  • 可靠性問題
    全域性的sequence table是單點的,存在單點可靠性問題。即使做了主從也是有百毫秒同步的時間延遲。同時切換也是需要時間的。

同時這個問題也是為什麼Mongodb作為分散式資料庫使用自增ID是不合適的原因,可以參考Mongodb自增ID不好

3. UUID

UUID的資訊參考:UUID

簡單的來講就是UUID長度是128byte,也就是32個16進位制數,至於切分通常形式為xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。

優點

  • 效能非常高:本地生成,沒有網路消耗。

缺點

  • 不易於儲存:UUID太長,16位元組128位,通常以36長度的字串表示,很多場景不適用。

  • 資訊不安全:基於MAC地址生成UUID的演算法可能會造成MAC地址洩露,這個漏洞曾被用於尋找梅麗莎病毒的製作者位置。

  • ID作為主鍵時在特定的環境會存在一些問題,比如做DB主鍵的場景下,UUID就非常不適用:

    • MySQL官方有明確的建議主鍵要儘量越短越好,36個字元長度的UUID不符合要求。

    • 對MySQL索引不利:如果作為資料庫主鍵,在InnoDB引擎下,使用的是聚簇索引,UUID的無序性會引起資料位置頻繁變動,嚴重影響效能。

4. 多臺Mysql伺服器

既然MySQL可以產生自增ID,那麼用多臺MySQL伺服器,能否組成一個高效能的分散式發號器呢?

我們可以使用多臺Mysql,利用給欄位設定auto_increment_incrementauto_increment_offset來保證ID自增。同時使用Mysql的replace into特性。sql操作類似如下:

begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;

假設在8臺Mysql伺服器下,第一臺MySQL初始值是1,每次自增8,第二臺MySQL初始值是2,每次自增8,依次類推。前面用一個 round-robin load balancer 擋著,每來一個請求,由 round-robin balancer 隨機地將請求發給8臺MySQL中的任意一個,然後返回一個ID。

這種方案的特點:

  • 實現簡單
  • 系統水平擴充套件比較困難,比如定義好了步長和機器臺數之後,如果要新增機器該怎麼做?假設現在只有一臺機器發號是1,2,3,4,5(步長是1),這個時候需要擴容機器一臺。可以這樣做:把第二臺機器的初始值設定得比第一臺超過很多,比如14(假設在擴容時間之內第一臺不可能發到14),同時設定步長為2,那麼這臺機器下發的號碼都是14以後的偶數。然後摘掉第一臺,把ID值保留為奇數,比如7,然後修改第一臺的步長為2。讓它符合我們定義的號段標準,對於這個例子來說就是讓第一臺以後只能產生奇數。擴容方案看起來複雜嗎?貌似還好,現在想象一下如果我們線上有100臺機器,這個時候要擴容該怎麼做?簡直是噩夢。所以系統水平擴充套件方案複雜難以實現。
  • ID沒有了單調遞增的特性,只能趨勢遞增,這個缺點對於一般業務需求不是很重要,可以容忍。
  • 資料庫壓力還是很大,每次獲取ID都得讀寫一次資料庫,只能靠堆機器來提高效能。

5. Twitter Snowflake

Twitter-Snowflake演算法產生的背景相當簡單,為了滿足Twitter每秒上萬條訊息的請求,每條訊息都必須分配一條唯一的id,這些id還需要一些大致的順序(方便客戶端排序),並且在分散式系統中不同機器產生的id必須不同。

Snowflake的原理和UUID,Mongodb ObjectId的原理類似都是以劃分名稱空間來生成ID的一種演算法。

Snowflake把時間戳,工作機器id,序列號組合在一起。

41-bit的時間可以表示(1L<<41)/(1000L360024*365)=69年的時間,10-bit機器可以分別表示1024臺機器。如果我們對IDC劃分有需求,還可以將10-bit分5-bit給IDC,分5-bit給工作機器。這樣就可以表示32個IDC,每個IDC下可以有32臺機器,可以根據自身需求定義。12個自增序列號可以表示2^12個ID,理論上snowflake方案的QPS約為409.6w/s,這種分配方式可以保證在任何一個IDC的任何一臺機器在任意毫秒內生成的ID都是不同的。

這種方式的優缺點是:

優點:

  • 毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。
  • 不依賴資料庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的效能也是非常高的。
  • 可以根據自身業務特性分配bit位,非常靈活。

缺點:

  • 強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重複或者服務會處於不可用狀態。

6. 美團Leaf

參考文章:

https://tech.meituan.com/2017/04/21/mt-leaf.html

https://www.jianshu.com/p/54a87a7c3622

https://soulmachine.gitbooks.io/system-design/content/cn/distributed-id-generator.html