1.背景
數(shù)據(jù)庫在使用過程中通常會受到網(wǎng)絡(luò)資源, CPU, 內(nèi)存,磁盤等資源的制約。在以上資源確定的前提下,如果上層系統(tǒng)對數(shù)據(jù)庫的使用訴求超出了它的承載能力,比如并發(fā)量太高,存儲的數(shù)據(jù)量太大。那么就會導(dǎo)致請求超時、系統(tǒng)響應(yīng)慢或者數(shù)據(jù)存儲能力不足等問題。
2.方法
為了解決如上問題就需要考慮數(shù)據(jù)庫的擴展,或者表的拆分,期望通過這些方式來降低數(shù)據(jù)庫的負(fù)載,提升系統(tǒng)的處理能力。通常的處理方式包括兩種,分別是主從復(fù)制,分庫分表。
3.作用
以上講述的方法分別解決的問題也是不同的。主從復(fù)制解決讀多寫少,單機讀取壓力大的問題。分庫分表則是解決單機并發(fā)大引起的寫入能力不足、數(shù)據(jù)存儲量大造成的查詢性能低下的問題。本文只討論分庫分表相關(guān)問題。
分庫分表
通過數(shù)據(jù)庫資源的橫向擴展,提升系統(tǒng)對外的整體處理能力、存儲能力
降低單表數(shù)據(jù)量,提升單表查詢性能
分散請求,降低單機請求負(fù)載
4.方式
分庫分表方式總體上分為垂直拆分和水平拆分。垂直拆分包括 垂直分表,垂直分庫;水平拆分包括水平分表,水平分庫分表;通常的拆分順序是先垂直分表,再垂直分庫,然后水平分表,最后水平分庫分表。因為從拆分的難道,以及拆分后系統(tǒng)的復(fù)雜度,運維的復(fù)雜度來講,其程度是逐漸遞增的。系統(tǒng)復(fù)雜度從中間件的引入,分布式事務(wù)的處理等方面產(chǎn)生影響;運維復(fù)雜度則是從中間件的維護成本上帶來影響。幾種方式體現(xiàn)了AKF 擴展立方體(《架構(gòu)即未來》提出,這里不再單獨介紹)Y軸擴展(按服務(wù),功能拆分)和Z軸擴展(數(shù)據(jù)分區(qū))。

垂直拆分:
1.垂直分表
即大表拆小表,體現(xiàn)在單表字段數(shù)太多,或者冗余字段數(shù)太多的情況。將使用不頻繁,數(shù)據(jù)冗余,字段長度太長(如text類型)的字段拆到擴展表里面。

優(yōu)點:通過冗余字段和其余字段數(shù)量上的減少降低單表的存儲占用
缺點:業(yè)務(wù)處理會存在多次查詢或者Join 查詢的問題
2.垂直分庫
就是將一個庫內(nèi)的表拆分到多個數(shù)據(jù)庫,包括物理拆分和邏輯拆分。物理拆分是拆分到不同機器上,邏輯拆分是分到不同Schema(此種方式不能解決資源限制引起的問題,因此不納入垂直分庫考慮的范圍。通常指的垂直分庫是指第一種方式)。常用的拆分方式是根據(jù)業(yè)務(wù)模塊做拆分,比如用戶相關(guān)的表拆到用戶庫,訂單相關(guān)的表拆到訂單庫,商品相關(guān)的表分到商品庫。

為什么這樣拆分?
可以想象一下如下場景:一個電商系統(tǒng),如果訂單量激增,那么原本除了需要存儲訂單,還需存儲用戶、商品、庫存的數(shù)據(jù)庫,就無法存儲訂單外的數(shù)據(jù)了,沒準(zhǔn)訂單都存不了,怎么辦。 如果下單相關(guān)的并發(fā)量太大,那么用戶、商品、庫存相關(guān)的請求就無法正常處理,同理也可能訂單相關(guān)的請求也無法處理,怎么辦。通過垂直分庫就能解決如上問題
優(yōu)點:通過資源橫向擴展,提升整體存儲能力,通過請求分散提升系統(tǒng)整體處理能力
缺點:出現(xiàn)分布式事務(wù)問題
水平拆分:
1.水平分表
針對數(shù)據(jù)量很大的表(如訂單表),進行單表橫向擴展。通常的方式有范圍分片(Range), 哈希分片(Hash),時間分片。

優(yōu)點:解決單表數(shù)據(jù)量大導(dǎo)致的查詢性能低的問題
缺點:不能解決單表數(shù)據(jù)量大導(dǎo)致的單機存儲空間不足的問題
2.水平分庫分表
針對數(shù)據(jù)量大的表進行水平拆分的同時,將表拆分到不同的物理庫。

優(yōu)點:解決單表數(shù)據(jù)量大,單機存儲不足的問題;并分散請求,解決單機負(fù)載高的問題
缺點:單表數(shù)據(jù)分散到不同數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)分散。查詢,統(tǒng)計,排序,Join 處理的復(fù)雜度增大
分片規(guī)則
1.范圍分片(Range)
按照ID 的范圍進行分片,比如 ID 1 ~ 5000 一個庫,ID 5001 ~ 10000 一個庫

優(yōu)點:天然分片,擴展不需要數(shù)據(jù)遷移
缺點:數(shù)據(jù)集中,易引起單機負(fù)載過大的問題
2.哈希分片(Hash)
先將分片字段Hash ,然后再根據(jù)機器數(shù)量取模進行分片

優(yōu)點:數(shù)據(jù)分散均勻
缺點:不易擴展,擴展需要遷移數(shù)據(jù)(使用一致性Hash 算法可解決此問題)
3.時間分片
根據(jù)時間做分片,比如分成某一年1 ~ 3月,某一年4 ~ 6月兩個分片

優(yōu)點:天然分片,易擴展,也方便做歷史數(shù)據(jù)遷移。適合訂單這類跟時間順序關(guān)聯(lián)強的數(shù)據(jù)。
缺點:數(shù)據(jù)集中,易引起單機負(fù)載過大的問題。并且單庫數(shù)據(jù)量無法準(zhǔn)確確定。
5.場景
垂直拆分:
垂直分庫在傳統(tǒng)的單體系統(tǒng)中,當(dāng)數(shù)據(jù)庫在并發(fā)出現(xiàn)瓶頸,單庫存儲資源不足等情況下比較適用
垂直分表在傳統(tǒng)的單體系統(tǒng)、現(xiàn)在的微服務(wù)架構(gòu)系統(tǒng),中臺架構(gòu)系統(tǒng)里。當(dāng)表設(shè)計不合理(字段過多,冗余字段太多,存在長度太長字段)的情況下適用
水平拆分:
水平分表在傳統(tǒng)單體系統(tǒng)、現(xiàn)在微服務(wù)架構(gòu)系統(tǒng)、中臺架構(gòu)系統(tǒng)中,出現(xiàn)單表數(shù)據(jù)量太大,單機數(shù)據(jù)存儲不足,單機請求負(fù)載太大的情況下適用。但通常作為主庫數(shù)據(jù)來講,數(shù)據(jù)量大和請求量大是成正比的。因此這種場景通常不在考慮范圍內(nèi)。
水平分庫分表在傳統(tǒng)的單體系統(tǒng)中,如今的微服務(wù)架構(gòu)或者中臺架構(gòu)場景中。當(dāng)單機并發(fā)量大,單表數(shù)據(jù)量大,單機存儲資源不足的情況下適用
6.現(xiàn)狀
垂直分表對于現(xiàn)如今的微服務(wù)架構(gòu)或者中臺架構(gòu)來講。數(shù)據(jù)庫本身已經(jīng)根據(jù)業(yè)務(wù)模塊進行了拆分,所以需要考慮垂直分庫的場景很少;數(shù)據(jù)庫表在設(shè)計之初已經(jīng)考慮表字段的個數(shù)多,冗余字段多,長度太長字段的問題,并進行了處理,所以需要垂直分表的場景也很少。
至于水平分表,如上面場景所分析,需要考慮的場景也是很少。
反觀現(xiàn)如今系統(tǒng)出現(xiàn)的并發(fā)量大,存儲資源不足,數(shù)據(jù)量大等問題。水平分庫分表才是亟待解決的問題,如下著重分析一下水平分庫分表。
7.什么場景下做分庫分表
分庫分表是通過分庫解決了單機存儲限制的問題,以及通過分散請求解決了單機請求壓力大的問題。然而分表為分庫擴展單機存儲能力提供了條件的同時,分表最重要的解決了單表數(shù)據(jù)量太大的問題。
那么單表數(shù)據(jù)量大會帶來什么問題呢?
對于 MySQL 來講通常存儲用的是InnoDB作為存儲引擎。那么它的查詢效率受索引B+ 樹數(shù)據(jù)結(jié)構(gòu)的影響。對于眾多數(shù)據(jù)結(jié)構(gòu)來講,B+樹從算法的層面講被DB來使用是無可挑剔的。通過控制樹的高度,減少IO 操作次數(shù)來提升查詢的效率。但話說回來,算法的優(yōu)勢不在本文討論的范圍內(nèi)。對于使用B+樹作為索引的InnoDB 來講,查詢效率還受Buffer Size的影響。Buffer Size 受內(nèi)存資源的影響。內(nèi)存不足的情況下,如果數(shù)據(jù)量太大就會造成磁盤檢索次數(shù)增多,造成IO操作變多,那么查詢效率就低了。
那么數(shù)據(jù)量達多大需要進行分庫分表?
從行業(yè)經(jīng)驗來講通常對于MySQL數(shù)據(jù)量達500W或者存儲達2G 的表,或者未來三年會達到這個量級的表就需要做分庫分表。當(dāng)然這也是行業(yè)跟常用的系統(tǒng)資源做出的一個初略判斷準(zhǔn)則。具體量級受系統(tǒng)資源限制。不過這個準(zhǔn)則可以作為我們系統(tǒng)架構(gòu)的一個判斷標(biāo)準(zhǔn),為系統(tǒng)的未來業(yè)務(wù)承載量做好準(zhǔn)備
8.舉例
本文以電商領(lǐng)域比較常見的訂單(Order)場景分析。
8.1 分片規(guī)則:
訂單在電商領(lǐng)域通常數(shù)據(jù)量是很大的。并且有個特性是通常會以時間維度檢索近段時間內(nèi)的數(shù)據(jù)。那么采用時間分片的策略對表進行分庫分庫。
8.2 中間件選擇:
8.2.1 中間件介紹
1. Sharing - JDBC 是當(dāng)當(dāng)開源的輕量級的分庫分表中間件,在Java 的JDBC 層提供額外服務(wù)

優(yōu)點:輕量級,無須單獨部署服務(wù),不存在單點問題
缺點: 應(yīng)用感知分庫分表,對代碼有侵入性
2. MyCat 是一個實現(xiàn)了MySQL協(xié)議的分庫分表數(shù)據(jù)庫代理中間件

優(yōu)點:分庫分表對應(yīng)用透明,對業(yè)務(wù)無侵入性
缺點: 存在單點問題,需要考慮高可用架構(gòu);多一層增大網(wǎng)絡(luò)開銷
8.2.2 選擇結(jié)果:
本著中間件盡量不對代碼造成侵入,讓應(yīng)用對分庫分表無感知的原則。這里選擇MyCat作為分庫分表中間件。

8.3 問題
隨著數(shù)據(jù)庫的分庫分表,一些問題也隨之而來,常見的問題有如下幾種
-
分布式全局唯一ID
數(shù)據(jù)拆分后,原有的數(shù)據(jù)庫自增主鍵已無法保證自增主鍵的全局唯一性,因此MyCat 提供了全局唯一ID生成機制。全局唯一ID 生成的方式如下:
-
本地文件方式:此方式 MyCat 將 sequence 配置到文件中,當(dāng)使用到 sequence 中的配置后,MyCat 會更新classpath 中 sequence_conf.properties 文件里 sequence 的當(dāng)前值。(不推薦)
優(yōu)點:本地加載,讀取速度較快。
缺點:當(dāng) MyCat 重新發(fā)布后,配置文件中的 sequence 會恢復(fù)到初始值。
-
數(shù)據(jù)庫方式:在數(shù)據(jù)庫中建立一張表,存放 sequence 名稱(name),sequence 當(dāng)前值(current_value),步長(increment 是int 類型,表示每次讀取多少個 sequence,假設(shè)為 K)等信息;
優(yōu)點:能夠持久化序列號
缺點:依賴數(shù)據(jù)庫,數(shù)據(jù)庫的穩(wěn)定性影響生成序列號生成。如果讀取序列號之后,MyCat 服務(wù)重啟了,會造成序列號不連續(xù)
-
本地時間戳: 64 位二進制 (42(毫秒)+5(機器 ID)+5(業(yè)務(wù)編碼)+12(重復(fù)累加) 類似雪花算法
優(yōu)點:不需要依賴外部系統(tǒng),能支持高并發(fā)高場景ID 的生成
缺點:高可用時需要同步全局時鐘,否則可能因為時間回溯導(dǎo)致ID重復(fù)
-
-
分布式事務(wù)
數(shù)據(jù)拆分后會面臨分布式事務(wù)問題,因此需要分布式的處理方案。
- MyCat支持XA分布式事務(wù)(配合MySQL 版本5.7及以上)
-
跨庫Join
Join 是關(guān)系型數(shù)據(jù)庫中最常用的一個特性,然而在分布式環(huán)境中, 跨分片的 Join 卻是最復(fù)雜的,最難解決的一 個問題。因此建議能不用Join盡量不用。同時MyCat 也提供了幾種Join處理方案
全局表:業(yè)務(wù)系統(tǒng)中通常存在一些字典表。這些字典表存在變動頻率低,數(shù)據(jù)量小(不超過10萬)等特點。MyCat 將這些表定義為全局表。將字典表或者符合字典表特性的一些表定義為全局表。通過此方式,很好的解決了數(shù)據(jù) Join 的難題。以全局表 + 基于 E-R 關(guān)系的分片策略,MyCat可以滿足 80%以上的企業(yè)應(yīng)用開發(fā)。
ER Join:MyCat 借鑒了 NewSQL 領(lǐng)域的新秀 Foundation DB 的設(shè)計思路,其將子表的存儲位置依賴于主表,并且物理上緊鄰存放,因此徹底解決了 Join 的效率和性能問題,根據(jù)這一思路,提出了基于 E-R 關(guān)系的數(shù)據(jù)分片策略,子表的記錄與所關(guān)聯(lián)的父表記錄存放在同一個數(shù)據(jù)分片上。例如訂單作為父表,訂單明細(xì)作為子表。存儲明細(xì)時講數(shù)據(jù)存到對應(yīng)訂單主表的分片上。
Share Join:Share Join 是一個簡單的跨分片 Join。目前支持 2 個表的 Join, 原理就是解析 SQL 語句,拆分成單表的 SQL 語句執(zhí)行,然后把各個節(jié)點的數(shù)據(jù)匯集。
Spark/Storm 對 Join 擴展: MyCat 后續(xù)的功能會引入 Spark 和 Storm 來做跨分片的 join。大致流程是這樣的: MyCat 調(diào)用 Spark, Storm的 API, 先把數(shù)據(jù)傳送到 Spark, Storm中; 然后在 Spark, Storm 里面進行 Join; 之后把數(shù)據(jù)傳回 MyCat ; 最后MyCat 再返回給客戶端。
-
跨庫 分頁、group by、order by、sum、count、max
MyCat 支持?jǐn)?shù)據(jù)的多片自動路由與聚合,支持sum, count, max等常用的聚合函數(shù), 支持跨庫分頁
分頁: 對于limit n,limit m,n 查詢均有處理方案。對limit n 的形式 MyCat 會返回最先回復(fù)結(jié)果的分片數(shù)據(jù),這樣可能會引起每次返回的結(jié)果不一樣。因此這種查詢需要加上排序。 對于limit m,n,MyCat會改寫sql ,在每個節(jié)點執(zhí)行l(wèi)imit m+n ,再在MyCat 內(nèi)部做最小堆運算。這種情況下需要開啟非堆內(nèi)存,否則很容易造成OOM。
group by、order by、sum、count、max:均會在每個分片內(nèi)執(zhí)行,然后在Mycat 內(nèi)部進行處理
9. 結(jié)語
追本溯源分庫分表是一項實現(xiàn)難度高,維護成本大的技術(shù)。它的設(shè)計初衷是為了解決大型系統(tǒng)數(shù)據(jù)庫遇到的瓶頸問題。不到萬不得已不要輕易使用。
任何技術(shù)選型都不能脫離實際問題去思考。所有技術(shù)都是圍繞它要解決的問題而誕生的,因此技術(shù)的選型應(yīng)該明確當(dāng)前的場景是什么,要解決的問題是什么,此種技術(shù)是否能解決這個問題,有沒有更簡單、更易維護、更易上手的技術(shù)解決此問題。
沒有萬能的技術(shù),只有解決對應(yīng)問題的技術(shù)。任何一項高大上的技術(shù)用在了錯誤的場景,結(jié)果就是添油加醋,得不償失
參考文檔
MyCat 權(quán)威指南: http://www.mycat.org.cn/document/mycat-definitive-guide.pdf