當資料庫扼住系統性能咽喉,直接分庫分表能解決嗎
眾所周知,資料庫很容易成為應用系統的瓶頸。單機資料庫的資源和處理能力有限,在高併發的分散式系統中,可採用分庫分表突破單機侷限。
本文總結了分庫分表的相關概念、全域性ID的生成策略、分片策略、平滑擴容方案及流行的方案。
一、分庫分表概述
在業務量不大時,單庫單表即可支撐。 當資料量過大儲存不下、或者併發量過大負荷不起時,就要考慮分庫分表。
1、分庫分表相關術語
-
讀寫分離: 不同的資料庫,同步相同的資料,分別只負責資料的讀和寫;
-
分割槽: 指定分割槽列表達式,把記錄拆分到不同的區域中(必須是同一伺服器,可以是不同硬碟),應用看來還是同一張表,沒有變化;
-
分庫: 一個系統的多張資料表,儲存到多個數據庫例項中;
-
分表: 對於一張多行(記錄)多列(欄位)的二維資料表,又分兩種情形:
①垂直分表: 豎向切分,不同分表儲存不同的欄位,可以把不常用或者大容量、或者不同業務的欄位拆分出去;
②水平分表 (最複雜):橫向切分,按照特定分片演算法,不同分表儲存不同的記錄。
2、真的要採用分庫分表?
需要注意的是,分庫分表會為資料庫維護和業務邏輯帶來一系列複雜性和效能損耗,除非預估的業務量大到萬不得已,切莫過度設計、過早優化。
規劃期內的資料量和效能問題,嘗試能否用下列方式解決:
-
當前資料量: 如果沒有達到幾百萬,通常無需分庫分表;
-
資料量問題: 增加磁碟、增加分庫(不同的業務功能表,整表拆分至不同的資料庫);
-
效能問題: 升級CPU/記憶體、讀寫分離、優化資料庫系統配置、優化資料表/索引、優化SQL、分割槽、資料表的垂直切分;
如果仍未能奏效,才考慮最複雜的方案:資料表的水平切分。
二、全域性ID生成策略
1、自動增長列
-
優點: 資料庫自帶功能,有序,效能佳。
-
缺點: 單庫單表無妨,分庫分表時如果沒有規劃,ID可能重複。
解決方案:
設定自增偏移和步長:
##假設總共有10個分表 ##級別可選:SESSION(會話級),GLOBAL(全域性) SET@@SESSION.auto_increment_offset=1;##起始值,分別取值為1~10 SET@@SESSION.auto_increment_increment=10;##步長增量
如果採用該方案,在擴容時需要遷移已有資料至新的所屬分片。
全域性ID對映表:
在全域性Redis中為每張資料表建立一個ID的鍵,記錄該表當前最大ID; 每次申請ID時,都自增1並返回給應用;Redis要定期持久至全域性資料庫。
2、UUID(128位)
在一臺機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。通常平臺會提供生成UUID的API。
UUID由4個連字號(-)將32個位元組長的字串分隔後生成的字串,總共36個位元組長。 形如:
550e8400-e29b-41d4-a716-446655440000。
UUID的計算因子包括:乙太網卡地址、納秒級時間、晶片ID碼和許多可能的數字。UUID是個標準,其實現有幾種,最常用的是微軟的GUID(GlobalsUniqueIdentifiers)。
-
優點: 簡單,全球唯一。
-
缺點: 儲存和傳輸空間大,無序,效能欠佳。
3、COMB(組合)
組合GUID(10位元組)和時間(6位元組),達到有序的效果,提高索引效能。
4、Snowflake(雪花)演算法
Snowflake是Twitter開源的分散式ID生成演算法,其結果為long(64bit)的數值。 其特性是各節點無需協調、按時間大致有序、且整個叢集各節點單不重複。
該數值的預設組成如下(符號位之外的三部分允許個性化調整):
-
1bit: 符號位,總是0(為了保證數值是正數);
-
41bit: 毫秒數(可用69年);
-
10bit: 節點ID(5bit資料中心+5bit節點ID,支援32*32=1024個節點);
-
12bit: 流水號(每個節點每毫秒內支援4096個ID,相當於409萬的QPS,相同時間內如ID遇翻轉,則等待至下一毫秒)。
三、分片策略
1、連續分片
根據特定欄位(比如使用者ID、訂單時間)的範圍,值在該區間的,劃分到特定節點。
-
優點: 叢集擴容後,指定新的範圍落在新節點即可,無需進行資料遷移。
-
缺點: 如果按時間劃分,資料熱點分佈不均(歷史數冷當前資料熱),導致節點負荷不均。
2、ID取模分片
-
缺點: 擴容後需要遷移資料。
3、一致性Hash演算法
-
優點: 擴容後無需遷移資料。
4、Snowflake分片
-
優點: 擴容後無需遷移資料。
四、分庫分表引入的問題
1、分散式事務
由於兩階段/三階段提交對效能損耗大,可改用事務補償機制。
2、跨節點JOIN
對於單庫JOIN,MySQL原生就支援; 對於多庫,出於效能考慮,不建議使用MySQL自帶的JOIN,可以用以下方案避免跨節點JOIN:
-
全域性表: 一些穩定的共用資料表,在各個資料庫中都儲存一份;
-
欄位冗餘: 一些常用的共用欄位,在各個資料表中都儲存一份;
-
應用組裝: 應用獲取資料後再組裝;
-
另外: 某個ID的使用者資訊在哪個節點,他的關聯資料(比如訂單)也在哪個節點,可以避免分散式查詢。
3、跨節點聚合
只能在應用程式端完成。 但對於分頁查詢,每次大量聚合後再分頁,效能欠佳。
4、節點擴容
節點擴容後,新的分片規則導致資料所屬分片有變,因而需要遷移資料。
五、節點擴容方案
1、常規方案
如果增加的節點數和擴容操作沒有規劃,那麼絕大部分資料所屬的分片都有變化,需要在分片間遷移:
-
預估遷移耗時,釋出停服公告;
-
停服(使用者無法使用服務),使用事先準備的遷移指令碼,進行資料遷移;
-
修改為新的分片規則;
-
啟動伺服器。
2、免遷移擴容
採用雙倍擴容策略,避免資料遷移。擴容前每個節點的資料,有一半要遷移至一個新增節點中,對應關係比較簡單。
具體操作如下(假設已有2個節點A/B,要雙倍擴容至A/A2/B/B2這4個節點):
-
無需停止應用伺服器;
-
新增兩個資料庫A2/B2作為從庫,設定主從同步關係為:A=>A2、B=>B2,直至主從資料同步完畢(早期資料可手工同步);
-
調整分片規則並使之生效:
原ID%2=0=>A改為ID%4=0=>A,ID%4=2=>A2;
原ID%2=1=>B改為ID%4=1=>B,ID%4=3=>B2。
-
解除資料庫例項的主從同步關係,並使之生效;
-
此時,四個節點的資料都已完整,只是有冗餘(多存了和自己配對的節點的那部分資料),擇機清除即可(過後隨時進行,不影響業務)。
六、分庫分表方案
1、代理層方式
部署一臺代理伺服器偽裝成MySQL伺服器,代理伺服器負責與真實MySQL節點的對接,應用程式只和代理伺服器對接。對應用程式是透明的。 比如MyCAT,官網,原始碼。
-
MyCAT後端可以支援MySQL、SQLServer、Oracle、DB2、PostgreSQL等主流資料庫,也支援MongoDB這種新型NoSQL方式的儲存,未來還會支援更多型別的儲存。
-
MyCAT不僅僅可以用作讀寫分離,以及分表分庫、容災管理,而且可以用於多租戶應用開發、雲平臺基礎設施,讓你的架構具備很強的適應性和靈活性。
2、應用層方式
處於業務層和JDBC層中間,是以JAR包方式提供給應用呼叫,對程式碼有侵入性。
主要方案有:
-
淘寶網的TDDL: 已於2012年關閉了維護通道,建議不要使用;
-
噹噹網的Sharding-JDBC: 仍在活躍維護中:噹噹應用框架ddframe中,從關係型資料庫模組dd-rdb中分離出來的資料庫水平分片框架,實現透明化資料庫分庫分表訪問,實現了Snowflake分片演算法。
Sharding-JDBC定位為輕量Java框架,使用客戶端直連資料庫,無需額外部署,無其他依賴,DBA也無需改變原有的運維方式。 Sharding-JDBC分片策略靈活,可支援等號、between、in等多維度分片,也可支援多分片鍵。
SQL解析功能完善,支援聚合、分組、排序、limit、or等查詢,並支援BindingTable以及笛卡爾積表查詢。
Sharding-JDBC直接封裝JDBC API,可以理解為增強版的JDBC驅動,舊程式碼遷移成本幾乎為零:
-
可適用於任何基於Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
-
可基於任何第三方的資料庫連線池,如DBCP、C3P0、BoneCP、Druid等。
-
理論上可支援任意實現JDBC規範的資料庫。雖然目前僅支援MySQL,但已有支援Oracle、SQLServer等資料庫的計劃。
參考
-
沈劍: 《資料庫秒級平滑擴容架構方案》
-
分散式事務的解決方案:
-
《The Cost of GUIDs as Primary Keys》:
-
<twitter/snowflake>:
https://github.com/twitter-archive/snowflake/tree/snowflake-2010
-
<Snowflake演算法詳解>:
以上就是我關於分庫分表的相關概念、全域性ID的生成策略、分片策略、平滑擴容方案及流行的方案的分享,歡迎大家留言討論!