由于業務需要,需要對Mysql數據庫進行分庫分表,故而最近一直在整理分庫分表的相關知識,現手上的工作也告一段落了,抽空將自己最近的學習結果轉化為博文,分享給大家,本博文打算做成一個系列的,首先是分庫分表的理論知識的了解,其次是基于Java編程語言的分庫分表的框架的開發,最后是分庫分表的編制。讓大家不僅僅從理論上了解mysql的分庫分表,通過代碼來更深層次的了解,理論是如何落地到實踐的。最后非常感謝《可伸縮服務架構 框架與中間件》這本書的作者么,本博文的代碼實現是參考此書,然后結合當前系統平臺框架開發而成。
堅持我寫作的一貫風格,我們先需要帶著問題來了解mysql的分庫分表
- 什么是分庫分表,為什么我們需要分庫分表
- 如何進行分庫分表,有什么優缺點
- 對于分庫分表有哪些架構設計,對于后期的擴容擴展怎么樣
- 目前行業內流行的解決方案有哪些?各自有什么特點
- 自己設計一個數據庫分庫分表的框架,如何設計,需要考慮哪些因素
為什么需要分庫分表
隨著我們的系統運行,存儲在關系型數據庫的數據量會越來越大,系統的訪問的壓力也會隨之增大,如果一個庫中的表數據超過了一定的數量,比如說mysql中的表數據達到千萬級別,就需要考慮進行分庫分表;
其次隨著表數據的不斷增大,會發現,查詢也隨著變得緩慢,如果添加索引的話,會發現影響到了新增和刪除的性能,如果我們將數據庫分散到不同的表上,單表的索引大小就得到了控制,對索引以及表結構的變更會變得很方便和高效;
當數據庫實例的吞吐量達到性能的瓶頸時,我們需要擴展數據庫實例,讓每個數據庫實例承擔其中一部分數據庫的請求,分解總體的大請求量的壓力;
在數據庫進行擴容的時候對應用層的配置改變最少, 就需要在每個數據庫實例中預留足夠的數據庫數量
以上的情況我們都可以使用分庫分表,那么什么是分庫分表呢?
簡而言之就是數據拆分:將一個表結構分為多個表,或者將一個表數據分片后放入多個表,這些表可以放在同一個數據庫里,也可以放到不同的數據庫中,甚至可以放到不同的數據庫實例中
數據拆分的方式
數據拆分有兩種方式:
- 垂直拆分: 根據業務的維度,將原本一個庫中的表拆分多個表,每個庫中表與原有的結構不同
- 水平拆分: 根據分片算法,將一個庫拆分成多個庫,每個庫依舊保留原有的結構
在實際的開發過程中,通常是先進行維度拆分形成微服務結構,然后再進行水平拆分
分庫分表
比如我們有一張表,隨著業務的不斷進行,mysql中表中數據量達到了10億,若是將數據存放在一張表中,則性能一定不會太好,根據我們使用的經驗,mysql數據庫一張表的數據記錄極限一般在5000萬左右,所以我們需要對進行分片存儲(水平拆分),按照5000萬一個單位來拆分的話,需要切片數量20個,也就是20個數據庫表
如果將20個相同業務表存放在同一個數據庫中,那么單個數據庫實例的網卡I/O、內存、CPU和磁盤性能是有限的,隨著數據庫訪問頻率的增加,會導致單個數據庫實例和數據庫達到性能瓶頸,因此我們需要將20個表分到多個數據庫和多個數據庫實例中,具體的評估如下:
【TODO 對數據庫實例和數據庫表的數量的評估】
如何進行分庫分表
分庫分表是對數據庫拆分的一種解決方案,根據實施切片邏輯的層次不同,我們將分庫分表方案大致分為三大類:客戶端分片、代理分片和支持事務的分布式數據庫
- 客戶端分片
所謂的客戶端分片即在使用數據庫的應用層直接操作分片邏輯,分片規則需要在同一個應用的多個節點間進行同步,每個應用層嵌入一個操作切片的邏輯實現。
在客戶端分片,目前主要有以下三種方式:
- 在應用層直接實現
這是一種非常通用的解決方案,直接在應用層讀取分片規則,解析分片規則,根據分片規則實現切分的路由邏輯,從應用層直接決定每次操作應該使用哪個數據庫實例中的對應的數據庫
這種解決方案雖然有一定的代碼侵入,但是實現起來比較簡單,但是切片的邏輯是自己開發的, 如果生產上遇到了問題,能快速定位解決;
當然這種方式也存在缺點:代碼的耦合度比較高,其次這種實現方式會讓數據庫保持的鏈接比較多,這要看應用服務的節點數量,需要提前進行容量上的評估
- 通過定制JDBC協議實現
這種解決方案主要是為了解決1中解決方案中的代碼耦合,通過定制JDBC協議來實現(主要是針對業務邏輯層提供與JDBC一致的接口),讓分庫分表在JDBC的內部實現
目前當當網開源的框架:Sharding JDBC 就是使用這種解決方案來實現的
- 通過定制ORM框架實現
目前ORM框架非常流行,流行的JPA、Mybatis和Hibernate都是優秀的ORM框架,通過定制ORM框架來實現分庫分表方案,常見的有基于Mybatis的分庫分表方案的解決;
<select id="selectUser" parameterType="java.util.Map" resultType="User">
select user_id as userId,user_name as userName
from user_#{index}
where user_id = #{userId}
</select>
- 代理分片
代理分片就是在應用層和數據庫層之間添加一個代理層,把分片的路由規則配置在代理層,代理層對外提供與JDBC兼容的接口給應用層,在業務實現之后,在代理層配置路由規則即可;
這種方案的優點:讓應用層的開發人員專注于業務邏輯的實現,把分庫分表的配置留給代理層處理
同樣的業務存在缺點:增加了代理層,這樣的話對每個數據庫操作都增加了一層網絡傳輸,這對性能是有影響的,同時需要維護增加的代理層,也有了硬件成本,線上生產環境出現了問題,不能迅速定位,需要有一定的技術專家來維護
我們常見的 Mycat就是基于此種解決方案來實現的
- 支持事務的分布式數據庫
支持分布式事務的框架,目前有OceanBase、TiDB框架,這些框架將可伸縮特定和分布式事務的實現包裝到了分布式數據庫內部實現,對使用者透明,使用者不需要直接控制這些特性,但是對事務的支持不如關系型數據,適合大數據日志系統、統計系統、查詢系統、社交網站等
分庫分表的架構設計
上面我們介紹過數據拆分的兩種方式:垂直拆分和水平拆分;
拆分方式 | 優點 | 缺點 |
---|---|---|
垂直拆分 | 1. 拆分后業務清晰,拆分規則明確 2. 系統之間進行整合或擴展容易 3. 按照成本、應用等級、應用的類型等將表放到不同的機器上,便于管理 4.便于實現動靜分離、冷熱分離的數據庫表的設計模式 5. 數據維護簡單 |
1. 部分業務表無法進行關聯、只能通過接口的方式來解決,提高了系統的復雜度 2. 受每種業務不同的限制,存在單庫性能瓶頸,對數據擴展和性能提升不友好 3. 事務處理復雜 |
水平拆分 | 1. 單褲單表的數據保持一定的量級,有助于性能的提高 2. 切分的表的結構相同,應用層改造較少,只需要增加路由規則即可 3. 提高了系統的穩定性和負載能力 |
1. 切分后數據是分散的,很難利用數據庫的關聯查詢,跨庫查詢性能較差 2. 拆分規則難以抽象 3. 分片數據的一致性難以解決 4. 數據擴容的難度和維護量極大 |
綜上所述,我們發現垂直拆分和水平拆分具有共同點:
- 存在分布式事務問題
- 存在跨節點join的問題
- 存在跨節點合并排序、分頁的問題
- 存在多數據源管理的問題
垂直拆分更偏向于業務拆分的過程,在技術上我們更傾向于水平切分的方案;
常見的分片策略:
- 按照哈希切片
對數據庫的某個字段進行來求哈希,再除以分片總數后取模,取模后相同的數據為一個分片,這樣將數據分成多個分片的方法叫做哈希分片
我們大多數在數據沒有時效性的情況下使用哈希分片,就是數據不管是什么時候產生的,系統都需要處理或者查詢;
優點 | 缺點 |
---|---|
數據切片比較均勻,數據壓力分散的效果好 | 數據分散后,對于查詢需求需要進行聚合處理 |
- 按照時間切片
按照時間的范圍將數據分布到不同的分片上,比如我們可以將交易數據按照與進行切片,或者按照季度進行切片,由交易數據的多少來決定按照什么樣的時間周期來進行切片
這種切片方式適合明顯時間特點的數據,常見的就是訂單歷史查詢
分布式事務
本博文不進行分布式事務的分析和實踐,后期我會更新一系列的分布式事務的博文,一起探討分布式事務的原理、解決方案和代碼實踐等,本博文簡單介紹了分布式事務的解決方案;
上面說到的,不管是垂直拆分還是水平拆分,都有一個共同的問題:分布式事務
我們將單表的數據切片后存儲在多個數據庫甚至是多個數據庫實例中,所以依靠數據庫本身的事務機制不能滿足需要,這時就需要用到分布式事務來解決了
三種解決方案
- 兩階段提交協議
兩階段提交協議中的兩階段是:準備階段和提交階段,兩個階段都是由事務管理器(協調者)發起,事務管理器能最大限度的保證跨數據庫操作的事務的原子性。
具體的交互邏輯如下:
優點 | 缺點 |
---|---|
是分布式系統環境下最嚴格的事務實現防范, 保證了數據一致性和操作原子性 |
1. 難以進行水平伸縮,因為在提交事務過程中,事務管理器需要和每個參與者進行準備和提交的操作協調 2.每個參與者之間的協調需要時間,參與者一多的話,則鎖定資源和消費資源之間的時間差就邊長 3. 兩階段提交協議是阻塞協議,在極端情況下不能快速響應的話,會造成阻塞問題 |
- 最大努力保證模式
這是一種非常通用的保證分布式一致性的模式,適合對一致性要求不是十分嚴格的但是對性能要求比較高的場景
最大努力保證模式:在更新多個資源時,將多個資源的提交盡量延后到最后一刻進行處理,這樣的話,如果業務流程出現問題,則所有的資源更新都可以回滾,事務仍然保持一致。
最大努力保證模式在發生系統問題,比如網絡問題等會出現問題,造成數據一致性的問題 ,這是就需要進行實時補償,將已提交的事務進行回滾
一般情況下,使用消息中間件來完成消費者之間的事務協調,客戶端從消息中間件的隊列中消費消息,更新數據庫,此時會涉及到兩個操作,一是從消息中間件消費消息,二是更新數據庫,具體的操作步驟如下:
- 開啟消息事務
- 接收消息
- 開啟數據庫事務
- 更新數據庫
- 提交數據庫事務
- 提交消息事務
上述步驟最關鍵的地方在5和6,如果5成功了,但是6出現了問題,導致消息中間件認為消息沒有被成功消費,既有的機制會重新再消費消息,就會出現消息重復消費,這是需要冪等處理來避免消息的重新消費
其次我們還需要注意消息消費的順序性問題,以及消費過程中是否調用遠程接口等耗時操作
優點 | 缺點 |
---|---|
性能較高 | 1. 數據一致性不能完美保證,只能是最大保證 2. 可能出現消息重復消費(冪等處理) 3. 數據庫事務可能存在遠程操作嵌套,互相影響 |
- 事務補償機制
以上提到的兩種解決方案:兩階段提交協議對系統的性能影響較大,最大努力保證模式會是多個分布式操作互相嵌套,有可能互相影響,那么我們采用事務補償機制:
事務補償即在事務鏈中的任何一個正向事務操作,都必須存在一個完全符合回滾規則的可逆事務。如果是一個完整的事務鏈,則必須事務鏈中的每一個業務服務或操作都有對應的可逆服務。對于Service服務本身無狀態,也不容易實現前面討論過的通過DTC或XA機制實現的跨應用和資源的事務管理,建立跨資源的事務上下文.
我們通過跨銀行轉賬來說明:
首先調用取款服務,完全調用成功并返回,數據已經持久化。然后調用異地的存款服務,如果也調用成功,則本身無任何問題。如果調用失敗,則需要調用本地注冊的逆向服務(本地存款服務),如果本地存款服務調用失敗,則必須考慮重試,如果約定重試次數仍然不成功,則必須log到完整的不一致信息。也可以是將本地存款服務作為消息發送到消息中間件,由消息中間件接管后續操作。
最后添加的重試機制是最大程度的確保補償服務執行,保持數據的一致性,如果重試之后還是失敗,則將操作保存在消息中間件中,等待后續處理,這樣就更多了一重保障
分庫分表引起的問題
由于將完整的數據分成若干份,在以下的場景中會產生多種問題
- 擴容與遷移
在分庫分表中,如果涉及的分片已經達到了承載數據的最大值,就需要對集群進行擴容,通常包括以下的步驟
- 按照新舊分片規則,對新舊數據庫進行雙寫
- 將雙寫前按照舊分片規則寫入的歷史數據,根據新分片規則遷移寫入新的數據庫
- 將按照舊的分片規則查詢改為按照新的分片規則查詢
- 將雙寫數據庫邏輯從代碼中下線,只按照新的分片規則寫入數據
- 刪除按照舊分片規則寫入的歷史數據
2步驟中遷移數據時,數據量非常大,通常會導致不一致,因此需要先遷移舊的數據,洗完后再遷移到新規則的新數據庫下,再做全量對比,對比評估在遷移過程中是否有數據的更新,如果有的話就再清洗、遷移,最后以對比沒有差距為準
- 分庫分表維度導致的查詢問題
進行了分庫分表以后,如果查詢的標準是分片的主鍵,則可以通過分片規則再次路由并查詢,但是對于其他主鍵的查詢、范圍查詢、關聯查詢、查詢結果排序等,并不是按照分庫分表維度查詢的;
這樣的話,解決方案有以下三種:
- 在多個分片表中查詢后合并數據集,這種方式的效率最低
- 冗余記錄多份數據,方便查詢, 缺點是需要額外維護一份數據,浪費資源
- 通過搜索引擎解決,但如果實時性要求很高,就需要實現實時搜索,可以利用大數據相關特性來解決
- 跨庫事務難以實現
同時操作多個庫,則會出現數據不一致的情況,此時可以引用分布式事務來解決
- 同組數據跨庫問題
要盡量把同一組數據放到同一數據庫服務器上,不但在某些場景下可以利用本地事務的強一致性,還可以是這組數據自治
主流的解決方案
目前針對mysql的分庫分表,行業內主流的解決方案有:ShardingJDBC、Mycat
Mycat代理分片框架
Mycat是一款面向企業級應用的開源數據庫中間件產品,他目前支持數據庫集群,分布式事務與ACID,被普遍視為基于Mysql技術的集群分布式數據庫解決方案
Mycat支持多種分片規則:
- 枚舉法
- 固定分片的hash算法
- 范圍約定
- 求模法
- 日期列分區法
- 通配取模
- ASCII碼求模通配
- 編程指定
- 截取數據哈希解析
- 一致性Hash
具體的Mycat使用方法,以后應該會有一博文來整理,敬請期待啊~~~