1、分區
對業務透明,分區只不過把存放數據的文件分成了許多小塊,例如mysql中的一張表對應三個文件.MYD,MYI,frm。
根據一定的規則把數據文件(MYD)和索引文件(MYI)進行了分割,分區后的表呢,還是一張表。分區可以把表分到不同的硬盤上,但不能分配到不同服務器上。
- 優點:數據不存在多個副本,不必進行數據復制,性能更高。
- 缺點:分區策略必須經過充分考慮,避免多個分區之間的數據存在關聯關系,每個分區都是單點,如果某個分區宕機,就會影響到系統的使用。
2、分片
對業務透明,在物理實現上分成多個服務器,不同的分片在不同服務器上。如HDFS。
3、分表
同庫分表:所有的分表都在一個數據庫中,由於數據庫中表名不能重復,因此需要把數據表名起成不同的名字。
- 優點:由於都在一個數據庫中,公共表,不必進行復制,處理更簡單。
- 缺點:由於還在一個數據庫中,CPU、內存、文件IO、網絡IO等瓶頸還是無法解決,只能降低單表中的數據記錄數。表名不一致,會導后續的處理復雜(參照mysql meage存儲引擎來處理)
不同庫分表:由於分表在不同的數據庫中,這個時候就可以使用同樣的表名。
- 優點:CPU、內存、文件IO、網絡IO等瓶頸可以得到有效解決,表名相同,處理起來相對簡單。
- 缺點:公共表由於在所有的分表都要使用,因此要進行復制、同步。一些聚合的操作,join,group by,order等難以順利進行。
4、分庫
分表和分區都是基於同一個數據庫里的數據分離技巧,對數據庫性能有一定提升,但是隨着業務數據量的增加,原來所有的數據都是在一個數據庫上的,網絡IO及文件IO都集中在一個數據庫上的,因此CPU、內存、文件IO、網絡IO都可能會成為系統瓶頸。
當業務系統的數據容量接近或超過單台服務器的容量、QPS/TPS接近或超過單個數據庫實例的處理極限等。此時,往往是采用垂直和水平結合的數據拆分方法,把數據服務和數據存儲分布到多台數據庫服務器上。
分庫只是一個通俗說法,更標准名稱是數據分片,采用類似分布式數據庫理論指導的方法實現,對應用程序達到數據服務的全透明和數據存儲的全透明
5、MyCat入門
用於數據庫分表分庫,讀寫分離的中間件。從http://www.mycat.io/下載即可。
MyCat作為數據庫中間件與代碼是弱關聯的,連接方式和普通數據庫一樣,如:jdbc:mysql://192.168.0.2:8066/
Mycat的配置文件都在conf目錄里面,這里介紹幾個常用的文件:
文件 | 說明 |
---|---|
server.xml | Mycat的配置文件,設置賬號、參數等 |
schema.xml | Mycat對應的物理數據庫和數據庫表的配置 |
rule.xml | Mycat分片(分庫分表)規則 |
server.xml:Mycat的配置文件,設置賬號、參數等。
<user name="test">
<property name="password">test</property>
<property name="schemas">lunch</property>
<property name="readOnly">false</property>
<!-- 表級 DML 權限設置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
user | 用戶配置節點 |
--name | 登錄的用戶名,也就是連接Mycat的用戶名 |
--password | 登錄的密碼,也就是連接Mycat的密碼 |
--schemas | 數據庫名,這里會和schema.xml中的配置關聯,多個用逗號分開,例如需要這個用戶需要管理兩個數據庫db1,db2,則配置db1,dbs |
--privileges | 配置用戶針對表的增刪改查的權限,具體見文檔 |
schema.xml:Mycat對應的物理數據庫和數據庫表的配置。
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <!-- 數據庫配置,與server.xml中的數據庫對應 --> <schema name="lunch" checkSQLschema="false" sqlMaxLimit="100"> <table name="lunchmenu" dataNode="dn1" /> <table name="restaurant" dataNode="dn1" /> <table name="userlunch" dataNode="dn1" /> <table name="users" dataNode="dn1" />
<!-- 分庫寫表 --> <table name="dictionary" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long" /> </schema> <!-- 分片配置 --> <dataNode name="dn1" dataHost="test1" database="lunch" /> <dataNode name="dn2" dataHost="test2" database="lunch" /> <!-- 物理數據庫配置 --> <dataHost name="test1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user();</heartbeat> <writeHost host="hostM1" url="192.168.0.2:3306" user="root" password="123456"> </writeHost> </dataHost> <dataHost name="test2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user();</heartbeat> <writeHost host="hostS1" url="192.168.0.3:3306" user="root" password="123456"> </writeHost> </dataHost> </mycat:schema>
參數 | 說明 |
---|---|
schema | 數據庫設置,此數據庫為邏輯數據庫,name與server.xml中schema對應 |
dataNode | 分片信息,也就是分庫相關配置 |
dataHost | 物理數據庫,真正存儲數據的數據庫 |
屬性 | 說明 |
---|---|
name | 邏輯數據庫名,與server.xml中的schema對應 |
checkSQLschema | 數據庫前綴相關設置,建議看文檔,這里暫時設為folse |
sqlMaxLimit | select 時默認的limit,避免查詢全表 |
屬性 | 說明 |
---|---|
name | 表名,物理數據庫中表名 |
dataNode | 表存儲到哪些節點,多個節點用逗號分隔。節點為下文dataNode設置的name |
primaryKey | 主鍵字段名,自動生成主鍵時需要設置 |
autoIncrement | 是否自增 |
rule | 分片規則名,具體規則下文rule詳細介紹 |
屬性 | 說明 |
---|---|
name | 節點名,與table中dataNode對應 |
datahost | 物理數據庫名,與datahost中name對應 |
database | 物理數據庫中數據庫名 |
屬性 | 說明 |
---|---|
name | 物理數據庫名,與dataNode中dataHost對應 |
balance | 均衡負載的方式 |
writeType | 寫入方式 |
dbType | 數據庫類型 |
heartbeat | 心跳檢測語句,注意語句結尾的分號要加。 |
rule.xml:Mycat分片(分庫分表)規則。
分表分庫案例:
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 數據庫配置,與server.xml中的數據庫對應 -->
<schema name="lunch" checkSQLschema="false" sqlMaxLimit="100">
<table name="lunchmenu" dataNode="dn1" />
<table name="restaurant" dataNode="dn1" />
<table name="userlunch" dataNode="dn1" />
<table name="users" dataNode="dn1" />
<table name="dictionary" primaryKey="id" autoIncrement="true" dataNode="dn1,dn2" rule="mod-long" />
</schema>
<!-- 分片配置 -->
<dataNode name="dn1" dataHost="test1" database="lunch" />
<dataNode name="dn2" dataHost="test2" database="lunch" />
<!-- 物理數據庫配置 -->
<dataHost name="test1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user();</heartbeat>
<writeHost host="hostM1" url="192.168.0.2:3306" user="root" password="123456">
</writeHost>
</dataHost>
<dataHost name="test2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user();</heartbeat>
<writeHost host="hostS1" url="192.168.0.3:3306" user="root" password="123456">
</writeHost>
</dataHost>
</mycat:schema>
PS: lunchmenu、restaurant、userlunch、users這些表都只寫入節點dn1,也就是192.168.0.2這個服務,而dictionary寫入了dn1、dn2兩個節點,也就是192.168.0.2、192.168.0.3這兩台服務器。
讀寫分離案例:
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <!-- 數據庫配置,與server.xml中的數據庫對應 --> <schema name="lunch" checkSQLschema="false" sqlMaxLimit="100"> <table name="lunchmenu" dataNode="dn1" /> <table name="restaurant" dataNode="dn1" /> <table name="userlunch" dataNode="dn1" /> <table name="users" dataNode="dn1" /> <table name="dictionary" primaryKey="id" autoIncrement="true" dataNode="dn1" /> </schema> <!-- 分片配置 --> <dataNode name="dn1" dataHost="test1" database="lunch" /> <!-- 物理數據庫配置 --> <dataHost name="test1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native"> <heartbeat>select user();</heartbeat> <writeHost host="hostM1" url="192.168.0.2:3306" user="root" password="123456"> <readHost host="hostS1" url="192.168.0.3:3306" user="root" password="123456"></readHost> </writeHost> </dataHost> </mycat:schema>
ps:MyCat沒有實現主從復制,需要使用數據庫本身自帶的這個功能來實現。
6、MyCat使用
##啟動
mycat start
##停止
mycat stop
##重啟
mycat restart
開發注意事項
1、非分片字段查詢,整庫查詢導致性能極差
Mycat中的路由結果是通過分片字段和分片方法來確定的。例如下圖中的一個Mycat分庫方案:
- 根據 tt_waybill 表的 id 字段來進行分片
- 分片方法為 id 值取 3 的模,根據模值確定在DB1,DB2,DB3中的某個分片
如果查詢條件中有 id 字段的情況還好,查詢將會落到某個具體的分片。例如:
MySQL>select * from tt_waybill where id = 12330;
此時Mycat會計算路由結果
12330 % 3 = 0 –> DB1
並將該請求路由到DB1上去執行。
如果查詢條件中沒有 分片字段 條件,例如:
mysql>select * from tt_waybill where waybill_no =88661;
此時Mycat無法計算路由,便發送到所有節點上執行:
DB1 –> select * from tt_waybill where waybill_no =88661;
DB2 –> select * from tt_waybill where waybill_no =88661;
DB3 –> select * from tt_waybill where waybill_no =88661;
如果該分片字段選擇度高,也是業務常用的查詢維度,一般只有一個或極少數個DB節點命中(返回結果集)。示例中只有3個DB節點,而實際應用中的DB節點數遠超過這個,假如有50個,那么前端的一個查詢,落到MySQL數據庫上則變成50個查詢,會極大消耗Mycat和MySQL數據庫資源。
如果設計使用Mycat時有非分片字段查詢,請考慮放棄!
2、分頁,沒有排序的情況下統一sql執行結果隨機選擇不同庫的查詢結果
先看一下Mycat是如何處理分頁操作的,假如有如下Mycat分庫方案:
一張表有30份數據分布在3個分片DB上,具體數據分布如下
DB1:[0,1,2,3,4,10,11,12,13,14]
DB2:[5,6,7,8,9,16,17,18,19]
DB3:[20,21,22,23,24,25,26,27,28,29]
(這個示例的場景中沒有查詢條件,所以都是全分片查詢,也就沒有假定該表的分片字段和分片方法)
當應用執行如下分頁查詢時
mysql>select * from table limit 2;
Mycat將該SQL請求分發到各個DB節點去執行,並接收各個DB節點的返回結果
DB1: [0,1]
DB2: [5,6]
DB3: [20,21]
但Mycat向應用返回的結果集取決於哪個DB節點最先返回結果給Mycat。如果Mycat最先收到DB1節點的結果集,那么Mycat返回給應用端的結果集為 [0,1],如果Mycat最先收到DB2節點的結果集,那么返回給應用端的結果集為 [5,6]。也就是說,相同情況下,同一個SQL,在Mycat上執行時會有不同的返回結果。
在Mycat中執行分頁操作時必須顯示加上排序條件才能保證結果的正確性,下面看一下Mycat對排序分頁的處理邏輯。
假如在前面的分頁查詢中加上了排序條件(假如表數據的列名為id)
mysql>select * from table order by id limit 2;
Mycat的處理邏輯如下圖:
在有排序呢條件的情況下,Mycat接收到各個DB節點的返回結果后,對其進行最小堆運算,計算出所有結果集中最小的兩條記錄 [0,1] 返回給應用。
但是,當排序分頁中有 偏移量 (offset)時,處理邏輯又有不同。假如應用的查詢SQL如下:
mysql>select * from table order by id limit 5,2;
如果按照上述排序分頁邏輯來處理,那么處理結果如下圖:
Mycat將各個DB節點返回的數據 [10,11], [16,17], [20,21] 經過最小堆計算后返回給應用的結果集是 [10,11]。可是,對於應用而言,該表的所有數據明明是 0-29 這30個數據的集合,limit 5,2 操作返回的結果集應該是 [5,6],如果返回 [10,11] 則是錯誤的處理邏輯。
所以Mycat在處理 有偏移量的排序分頁 時是另外一套邏輯——改寫SQL 。如下圖:
Mycat在下發有 limit m,n 的SQL語句時會對其進行改寫,改寫成 limit 0, m+n 來保證查詢結果的邏輯正確性。所以,Mycat發送到后端DB上的SQL語句是
mysql>select * from table order by id limit 0,7;
各個DB返回給Mycat的結果集是
DB1: [0,1,2,3,4,10,11]
DB2: [5,6,7,8,9,16,17]
DB3: [20,21,22,23,24,25,26]
經過最小堆計算后得到最小序列 [0,1,2,3,4,5,6] ,然后返回偏移量為5的兩個結果為 [5,6] 。
雖然Mycat返回了正確的結果,但是仔細推敲發現這類操作的處理邏輯是及其消耗(浪費)資源的。應用需要的結果集為2條,Mycat中需要處理的結果數為21條。也就是說,對於有 t 個DB節點的全分片 limit m, n 操作,Mycat需要處理的數據量為 (m+n)*t 個。比如實際應用中有50個DB節點,要執行limit 1000,10操作,則Mycat處理的數據量為 50500 條,返回結果集為10,當偏移量更大時,內存和CPU資源的消耗則是數十倍增加。
如果設計使用Mycat時有分頁排序,請考慮放棄!
3、任意表JOIN,關聯字段不在同一個庫導致查詢失敗
先看一下在單庫中JOIN中的場景。假設在某單庫中有 player 和 team 兩張表,player 表中的 team_id 字段與 team 表中的 id 字段相關聯。操作場景如下圖:
JOIN操作的SQL如下
mysql>select p_name,t_name from player p, team t where p.no = 3 and p.team_id = t.id;
此時能查詢出結果
p_name | t_name |
---|---|
Wade | Heat |
如果將這兩個表的數據分庫后,相關聯的數據可能分布在不同的DB節點上,如下圖:
這個SQL在各個單獨的分片DB中都查不出結果,也就是說Mycat不能查詢出正確的結果集。
設計使用Mycat時如果要進行表JOIN操作,要確保兩個表的關聯字段具有相同的數據分布,否則請考慮放棄!
4、分布式事務
Mycat並沒有根據二階段提交協議實現 XA事務,而是只保證 prepare 階段數據一致性的 弱XA事務 ,實現過程如下:
應用開啟事務后Mycat標識該連接為非自動提交,比如前端執行
mysql>begin;
Mycat不會立即把命令發送到DB節點上,等后續下發SQL時,Mycat從連接池獲取非自動提交的連接去執行。
Mycat會等待各個節點的返回結果,如果都執行成功,Mycat給該連接標識為 Prepare Ready 狀態,如果有一個節點執行失敗,則標識為 Rollback 狀態。
執行完成后Mycat等待前端發送 commit 或 rollback 命令。發送 commit 命令時,Mycat檢測當前連接是否為 Prepare Ready 狀態,若是,則將 commit 命令發送到各個DB節點。
但是,這一階段是無法保證一致性的,如果一個DB節點在 commit 時故障,而其他DB節點 commit 成功,Mycat會一直等待故障DB節點返回結果。Mycat只有收到所有DB節點的成功執行結果才會向前端返回 執行成功 的包,此時Mycat只能一直 waiting 直至TIMEOUT,導致事務一致性被破壞。
設計使用Mycat時如果有分布式事務,得先看是否得保證事務得強一致性,否則請考慮放棄!