MyCat分庫分表入門


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 字段相關聯。操作場景如下圖:

單個DB中JOIN

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節點上,如下圖:

分庫JOIN

這個SQL在各個單獨的分片DB中都查不出結果,也就是說Mycat不能查詢出正確的結果集。

設計使用Mycat時如果要進行表JOIN操作,要確保兩個表的關聯字段具有相同的數據分布,否則請考慮放棄!

4、分布式事務

Mycat並沒有根據二階段提交協議實現 XA事務,而是只保證 prepare 階段數據一致性的 弱XA事務 ,實現過程如下:

應用開啟事務后Mycat標識該連接為非自動提交,比如前端執行

mysql>begin;

Mycat不會立即把命令發送到DB節點上,等后續下發SQL時,Mycat從連接池獲取非自動提交的連接去執行。

弱XA事務1

Mycat會等待各個節點的返回結果,如果都執行成功,Mycat給該連接標識為 Prepare Ready 狀態,如果有一個節點執行失敗,則標識為 Rollback 狀態。

弱XA事務2

執行完成后Mycat等待前端發送 commit 或 rollback 命令。發送 commit 命令時,Mycat檢測當前連接是否為 Prepare Ready 狀態,若是,則將 commit 命令發送到各個DB節點。

弱XA事務3

但是,這一階段是無法保證一致性的,如果一個DB節點在 commit 時故障,而其他DB節點 commit 成功,Mycat會一直等待故障DB節點返回結果。Mycat只有收到所有DB節點的成功執行結果才會向前端返回 執行成功 的包,此時Mycat只能一直 waiting 直至TIMEOUT,導致事務一致性被破壞。

設計使用Mycat時如果有分布式事務,得先看是否得保證事務得強一致性,否則請考慮放棄!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM