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