數據庫Sharding的基本思想和切分策略
一、基本思想
Sharding的基本思想就要把一個數據庫切分成多個部分放到不同的數據庫(server)上,從而緩解單一數據庫的性能問題。不太嚴格的講,對於海量數據的數據庫,如果是因為表多而數據多,這時候適合使用垂直切分,即把關系緊密(比如同一模塊)的表切分出來放在一個server上。如果表並不多,但每張表的數據非常多,這時候適合水平切分,即把表的數據按某種規則(比如按ID散列)切分到多個數據庫(server)上。當然,現實中更多是這兩種情況混雜在一起,這時候需要根據實際情況做出選擇,也可能會綜合使用垂直與水平切分,從而將原有數據庫切分成類似矩陣一樣可以無限擴充的數據庫(server)陣列。下面分別詳細地介紹一下垂直切分和水平切分.
垂直切分的最大特點就是規則簡單,實施也更為方便,尤其適合各業務之間的耦合度非
常低,相互影響很小,業務邏輯非常清晰的系統。在這種系統中,可以很容易做到將不同業
務模塊所使用的表分拆到不同的數據庫中。根據不同的表來進行拆分,對應用程序的影響也
更小,拆分規則也會比較簡單清晰。(這也就是所謂的”share nothing”)。
水平切分於垂直切分相比,相對來說稍微復雜一些。因為要將同一個表中的不同數據拆
分到不同的數據庫中,對於應用程序來說,拆分規則本身就較根據表名來拆分更為復雜,后
期的數據維護也會更為復雜一些。
讓我們從普遍的情況來考慮數據的切分:一方面,一個庫的所有表通常不可能由某一張表全部串聯起來,這句話暗含的意思是,水平切分幾乎都是針對一小搓一小搓(實際上就是垂直切分出來的塊)關系緊密的表進行的,而不可能是針對所有表進行的。另一方面,一些負載非常高的系統,即使僅僅只是單個表都無法通過單台數據庫主機來承擔其負載,這意味着單單是垂直切分也不能完全解決問明。因此多數系統會將垂直切分和水平切分聯合使用,先對系統做垂直切分,再針對每一小搓表的情況選擇性地做水平切分。從而將整個數據庫切分成一個分布式矩陣。
二、切分策略
如前面所提到的,切分是按先垂直切分再水平切分的步驟進行的。垂直切分的結果正好為水平切分做好了鋪墊。垂直切分的思路就是分析表間的聚合關系,把關系緊密的表放在一起。多數情況下可能是同一個模塊,或者是同一“聚集”。這里的“聚集”正是領域驅動設計里所說的聚集。在垂直切分出的表聚集內,找出“根元素”(這里的“根元素”就是領域驅動設計里的“聚合根”),按“根元素”進行水平切分,也就是從“根元素”開始,把所有和它直接與間接關聯的數據放入一個shard里。這樣出現跨shard關聯的可能性就非常的小。應用程序就不必打斷既有的表間關聯。比如:對於社交網站,幾乎所有數據最終都會關聯到某個用戶上,基於用戶進行切分就是最好的選擇。再比如論壇系統,用戶和論壇兩個模塊應該在垂直切分時被分在了兩個shard里,對於論壇模塊來說,Forum顯然是聚合根,因此按Forum進行水平切分,把Forum里所有的帖子和回帖都隨Forum放在一個shard里是很自然的。
對於共享數據數據,如果是只讀的字典表,每個shard里維護一份應該是一個不錯的選擇,這樣不必打斷關聯關系。如果是一般數據間的跨節點的關聯,就必須打斷。
需要特別說明的是:當同時進行垂直和水平切分時,切分策略會發生一些微妙的變化。比如:在只考慮垂直切分的時候,被划分到一起的表之間可以保持任意的關聯關系,因此你可以按“功能模塊”划分表格,但是一旦引入水平切分之后,表間關聯關系就會受到很大的制約,通常只能允許一個主表(以該表ID進行散列的表)和其多個次表之間保留關聯關系,也就是說:當同時進行垂直和水平切分時,在垂直方向上的切分將不再以“功能模塊”進行划分,而是需要更加細粒度的垂直切分,而這個粒度與領域驅動設計中的“聚合”概念不謀而合,甚至可以說是完全一致,每個shard的主表正是一個聚合中的聚合根!這樣切分下來你會發現數據庫分被切分地過於分散了(shard的數量會比較多,但是shard里的表卻不多),為了避免管理過多的數據源,充分利用每一個數據庫服務器的資源,可以考慮將業務上相近,並且具有相近數據增長速率(主表數據量在同一數量級上)的兩個或多個shard放到同一個數據源里,每個shard依然是獨立的,它們有各自的主表,並使用各自主表ID進行散列,不同的只是它們的散列取模(即節點數量)必需是一致的。(
本文着重介紹sharding的基本思想和理論上的切分策略,關於更加細致的實施策略和參考事例請參考我的另一篇博文:數據庫分庫分表(sharding)系列(一) 拆分實施策略和示例演示
)
1.事務問題:
解決事務問題目前有兩種可行的方案:分布式事務和通過應用程序與數據庫共同控制實現事務下面對兩套方案進行一個簡單的對比。
方案一:使用分布式事務
優點:交由數據庫管理,簡單有效
缺點:性能代價高,特別是shard越來越多時
方案二:由應用程序和數據庫共同控制
原理:將一個跨多個數據庫的分布式事務分拆成多個僅處
於單個數據庫上面的小事務,並通過應用程序來總控
各個小事務。
優點:性能上有優勢
缺點:需要應用程序在事務控制上做靈活設計。如果使用
了spring的事務管理,改動起來會面臨一定的困難。
2.跨節點Join的問題
只要是進行切分,跨節點Join的問題是不可避免的。但是良好的設計和切分卻可以減少此類情況的發生。解決這一問題的普遍做法是分兩次查詢實現。在第一次查詢的結果集中找出關聯數據的id,根據這些id發起第二次請求得到關聯數據。
3.跨節點的count,order by,group by以及聚合函數問題
這些是一類問題,因為它們都需要基於全部數據集合進行計算。多數的代理都不會自動處理合並工作。解決方案:與解決跨節點join問題的類似,分別在各個節點上得到結果后在應用程序端進行合並。和join不同的是每個結點的查詢可以並行執行,因此很多時候它的速度要比單一大表快很多。但如果結果集很大,對應用程序內存的消耗是一個問題。
【數據庫】分區分表分庫、讀寫分離
一、什么是分區、分表、分庫
分區
就是把一張表的數據分成N個區塊,在邏輯上看最終只是一張表,但底層是由N個物理區塊組成的
分表
就是把一張表按一定的規則分解成N個具有獨立存儲空間的實體表。系統讀寫時需要根據定義好的規則得到對應的字表明,然后操作它。
分庫
一旦分表,一個庫中的表會越來越多
將整個數據庫比作圖書館,一張表就是一本書。當要在一本書中查找某項內容時,如果不分章節,查找的效率將會下降。而同理,在數據庫中就是分區。
二、常用的單機數據庫的瓶頸
問題描述
- 單個表數據量越大,讀寫鎖,插入操作重新建立索引效率越低。
- 單個庫數據量太大(一個數據庫數據量到1T-2T就是極限)
- 單個數據庫服務器壓力過大
- 讀寫速度遇到瓶頸(並發量幾百)
三、分區
什么時候考慮使用分區?
-
一張表的查詢速度已經慢到影響使用的時候。
-
sql經過優化
-
數據量大
- 表中的數據是分段的
-
對數據的操作往往只涉及一部分數據,而不是所有的數據
分區解決的問題
主要可以提升查詢效率
分區的實現方式(簡單)
mysql5 開始支持分區功能
CREATE TABLE sales ( id INT AUTO_INCREMENT, amount DOUBLE NOT NULL, order_day DATETIME NOT NULL, PRIMARY KEY(id, order_day) ) ENGINE=Innodb PARTITION BY RANGE(YEAR(order_day)) ( PARTITION p_2010 VALUES LESS THAN (2010), PARTITION p_2011 VALUES LESS THAN (2011), PARTITION p_2012 VALUES LESS THAN (2012), PARTITION p_catchall VALUES LESS THAN MAXVALUE);
四、分表
什么時候考慮分表?
-
一張表的查詢速度已經慢到影響使用的時候。
-
sql經過優化
- 數據量大
-
當頻繁插入或者聯合查詢時,速度變慢
分表解決的問題
分表后,單表的並發能力提高了,磁盤I/O性能也提高了,寫操作效率提高了
- 查詢一次的時間短了
- 數據分布在不同的文件,磁盤I/O性能提高
- 讀寫鎖影響的數據量變小
- 插入數據庫需要重新建立索引的數據減少
分表的實現方式(復雜)
需要業務系統配合遷移升級,工作量較大
分區和分表的區別與聯系
-
分區和分表的目的都是減少數據庫的負擔,提高表的增刪改查效率。
- 分區只是一張表中的數據的存儲位置發生改變,分表是將一張表分成多張表。
- 當訪問量大,且表數據比較大時,兩種方式可以互相配合使用。
-
當訪問量不大,但表數據比較多時,可以只進行分區。
常見分區分表的規則策略(類似)
- Range(范圍)
- Hash(哈希)
- 按照時間拆分
- Hash之后按照分表個數取模
- 在認證庫中保存數據庫配置,就是建立一個DB,這個DB單獨保存user_id到DB的映射關系
12306的訂單是如何存儲的?
五.分庫分表
當一張表隨着時間和業務的發展,庫里表的數據量會越來越大。數據操作也隨之會越來越大。一台物理機的資源有限,最終能承載的數據量、數據的處理能力都會受到限制。這時候就會使用分庫分表來承接超大規模的表,單機放不下的那種。
區別於分區的是,分區一般都是放在單機里的,用的比較多的是時間范圍分區,方便歸檔。只不過分庫分表需要代碼實現,分區則是mysql內部實現。分庫分表和分區並不沖突,可以結合使用。
3.1 實現
3.1.1 分庫分表標准
- 存儲占用100G+
- 數據增量每天200w+
- 單表條數1億條+
3.1.2 分庫分表字段
分庫分表字段取值非常重要
- 在大多數場景該字段是查詢字段
- 數值型
一般使用userId,可以滿足上述條件
3.2 分布式數據庫中間件
分布式數據庫中間件分為兩種,proxy和客戶端式架構。proxy模式有MyCat、DBProxy等,客戶端式架構有TDDL、Sharding-JDBC等。那么proxy和客戶端式架構有何區別呢?各自有什么優缺點呢?其實看一張圖便可知曉。
proxy模式的話我們的select和update語句都是發送給代理,由這個代理來操作具體的底層數據庫。所以必須要求代理本身需要保證高可用,否則數據庫沒有宕機,proxy掛了,那就走遠了。
客戶端模式通常在連接池上做了一層封裝,內部與不同的庫連接,sql交給smart-client進行處理。通常僅支持一種語言,如果其他語言要使用,需要開發多語言客戶端。
各自的優缺點如下:
3.3 內部文件
找了一個分庫分表+分區的例子,基本上和分區表的差不多,只是多了多了很多表的.ibd文件,上面有文件的解釋:
[miaojiaxing@Grim testmydata]# ls | grep 'base_info' base_info_00.frm base_info_00#P#p_2018.ibd base_info_00#P#p_2019.ibd base_info_00#P#p_2020.ibd base_info_00#P#p_2021.ibd base_info_00#P#p_init.ibd base_info_00#P#p_max.ibd base_info_01.frm base_info_01#P#p_2018.ibd base_info_01#P#p_2019.ibd base_info_01#P#p_2020.ibd base_info_01#P#p_2021.ibd base_info_01#P#p_init.ibd base_info_01#P#p_max.ibd base_info.frm base_info.ibd
3.4 問題
3.4.1 事務問題
既然分庫分表了,那么肯定涉及到分布式事務,如何保證插入到不同庫的多條記錄能夠要么同時成功,要么同時失敗。有些同學可能想到XA,XA性能差而且不需要使用mysql5.7。柔性事務是目前主流的方案,TCC模式就屬於柔性事務。
對於分布式事務問題每家公司有自己的實現,華為用saga,阿里用TXC,螞蟻用DTX,支持FMT模式和TCC模式。
3.4.2 join問題
tddl、MyCAT等都支持跨分片join。但是盡力避免跨庫join,比如通過字段冗余的方式等。
如果出現了這種情況且中間件支持分片join,那么可以這樣使用。如果不支持可以手工查詢。
六.總結
分表和在用途上不一樣,分表是為了承接超大規模的表,單機放不下那種(如果並發量不大,只是查找耗時,當然分表也可以放在單機里)。分區的話則一般都是放在單機里的,用的比較多的是時間范圍分區,方便歸檔。性能穩定上的話都是一個個子表,差不多,區別應該是分區表是mysql內部實現的,會比分表方案少一點數據交互
mysql的水平分表和垂直分表的區別
1,水平分割:
例:QQ的登錄表。假設QQ的用戶有100億,如果只有一張表,每個用戶登錄的時候數據庫都要從這100億中查找,會很慢很慢。如果將這一張表分成100份,每張表有1億條,就小了很多,比如qq0,qq1,qq1...qq99表。
用戶登錄的時候,可以將用戶的id%100,那么會得到0-99的數,查詢表的時候,將表名qq跟取模的數連接起來,就構建了表名。比如123456789用戶,取模的89,那么就到qq89表查詢,查詢的時間將會大大縮短。
這就是水平分割。
2,垂直分割:
垂直分割指的是:表的記錄並不多,但是字段卻很長,表占用空間很大,檢索表的時候需要執行大量的IO,嚴重降低了性能。這時需要把大的字段拆分到另一個表,並且該表與原表是一對一的關系。
例如學生答題表tt:有如下字段:
Id name 分數 題目 回答
其中題目和回答是比較大的字段,id name 分數比較小。
如果我們只想查詢id為8的學生的分數:select 分數 from tt where id = 8;雖然知識查詢分數,但是題目和回答這兩個大字段也是要被掃描的,很消耗性能。但是我們只關心分數,並不想查詢題目和回答。這就可以使用垂直分割。我們可以把題目單獨放到一張表中,通過id與tt表建立一對一的關系,同樣將回答單獨放到一張表中。這樣我們插敘tt中的分數的時候就不會掃描題目和回答了。
3,其他要點:
1)存放圖片、文件等大文件用文件系統存儲。數據庫只存儲路徑,圖片和文件存放在文件系統,甚至單獨存放在一台服務器(圖床)。
2)數據參數配置。
最重要的參數就是內存,我們主要用的innodb引擎,所以下面兩個參數調的很大:
innodb_additional_mem_pool_size=64M
innodb_buffer_pool_size=1G
對於MyISAM,需要調整key_buffer_size,當然調整參數還是要看狀態,用show status語句可以看到當前狀態,以決定該調整哪些參數。
4,合理的硬件資源和操作系統
如果機器的內存超過4G,那么應當采用64位操作系統和64位MySQL。
案例:
簡單購物系統暫設涉及如下表:
1.產品表(數據量10w,穩定)
2.訂單表(數據量200w,且有增長趨勢)
3.用戶表 (數據量100w,且有增長趨勢)
以mysql為例講述下水平拆分和垂直拆分,mysql能容忍的數量級在百萬靜態數據可以到千萬
垂直拆分:
解決問題:
表與表之間的io競爭
不解決問題:
單表中數據量增長出現的壓力
方案:
把產品表和用戶表放到一個server上
訂單表單獨放到一個server上
水平拆分:
解決問題:
單表中數據量增長出現的壓力
不解決問題:
表與表之間的io爭奪
方案:
用戶表通過性別拆分為男用戶表和女用戶表
訂單表通過已完成和完成中拆分為已完成訂單和未完成訂單
產品表 未完成訂單放一個server上
已完成訂單表盒男用戶表放一個server上
女用戶表放一個server上