MyCAT是一個開源的分佈式數據庫組件,在項目裏,一般用這個組件實現針對數據庫的分庫分表功能,從而提升對數據表,尤其是大數據庫表的訪問性能。而且在實際項目裏,MyCAT分庫分表組件一般會和MySQL以及Redis組件整合使用,這樣就能從“降低數據表裏數據量規模”和“緩存數據”這兩個維度提升對數據的訪問性能。
1 分庫分表概述
先通過一個實例來看下分庫分表的概念,比如在某電商系統裏,存在一張主鍵爲id的流水錶,如果該電商系統的業務量很大,這張流水錶很有可能達到“億”級規模,甚至更大。如果要從這張表裏查詢數據,哪怕用到索引等數據庫優化的措施,但畢竟數據表的規模太大,這會成爲性能上的瓶頸,所以可以按如下的思路拆分這張大的流水錶。
1 在不同的10個數據庫,同時創建這10張流水錶,這些表的表結構完全一致。
2 在1號數據庫裏,只存放id%10等於1的流水記錄,比如存放id是1、11和21等的流水記錄,在2號數據庫裏只存放id%10等於2的流水記錄,以此類推。
也就是說,通過上述步驟,能把這張流水錶拆分成10個字表,而MyCAT組件能把應用程序對流水錶的請求分散到10張子表裏,具體的效果下圖所示。
在實際項目裏,子表的個數可以根據實際需求來設置。由於把大表的數據分散到若干張子表裏,所以每次數據請求所面對的數據總量能有效降低,從中大家能感受到“分表”做法對提升數據庫訪問性能的幫助。
並且在實際項目裏,會盡量把子表分散創建到不同的主機上,而不是單純地在同一臺主機同一個數據庫上創建多個子表,也就是說,需要儘量把這些子表分散到不同的數據庫上,具體效果如下圖所示。
儘量對子表進行“分庫”還是出於提升性能的考慮。由於單臺數據庫處理請求時總會有性能瓶頸,比如每秒最多能處理500個請求。如果把這些子表放在同一臺主機的同一個數據庫上,那麼對該表的請求速度依然無法突破單臺數據庫的性能瓶頸。但如果把這些子表分散到不同主機的不同數據庫上,那麼對該表的請求就相當於被有效分攤到不同的數據庫上,這樣就能成n倍地提升數據庫的有效負載。
在實際項目裏,出於成本上的考慮,或許無法爲每個子表分配一臺主機,在這種情況下可以退而求其次,可以把不同的子表分散創建在同一主機的不同數據庫上,總之儘量別在同一主機同一數據庫上創建不同的子表。
也就是說,通過“分表”,能有效降低大表的數據規模,通過“分庫”,能整合多個數據庫,從而能提升處理請求的有效負載。而MyCAT分佈式數據庫組件,實現這種“分庫分表”的效果,所以通常就把它叫做“MyCAT分庫分表組件”。
事實上,MyCAT組件能解析SQL語句,並根據預先設置好的分庫字段和分庫規則,把該SQL發送到對應的子表上執行,再把執行好的結果再返回給應用程序。2 用MyCAT組件實現分庫分表
在上文裏已經提到,用MyCAT可以實現分庫分表的效果,該組件默認工作在8066端口,它和應用程序以及數據庫的關係如下圖所示。從中大家可以看到,Java應用程序不是直接和MySQL等數據庫互連,而是和MyCAT組件連接。應用程序是把SQL請求發送到MyCAT,而MyCAT根據配置好的分庫分表規則,把請求發送到對應的數據庫上,得到請求再返回給應用程序。
爲了實現分庫分表的效果,一般需要配置MyCAT組件裏如下表所示的三個文件。
這裏將以一個MyCAT組件連接三個數據庫爲例,具體給出上述三個配置文件的編寫範例。
第一,server.xml配置文件的代碼如下所示。
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mycat:server SYSTEM "server.dtd">
3 <mycat:server xmlns:mycat="http://io.mycat/">
4 <system>
5 <property name="serverPort">8066</property>
6 <property name="managerPort">9066</property>
7 </system>
8 <user name="root">
9 <property name="password">123456</property>
10 <property name="schemas">redisDemo</property>
11 </user>
12 </mycat:server>
在第5行和第6行裏,分別配置了該MyCAT組件的工作端口和管理端口爲8066和9066,在第8行到第11行的代碼裏,配置了連接該MyCAT組件的用戶名是root,連接密碼是123456,同時,該root登錄後,可以訪問MyCAT組件裏的redisDemo數據庫。
請注意這裏redisDemo是MyCAT組件的數據庫,而不是MySQL裏的,在實踐過程中,這個數據庫一般和MySQL裏的同名。
第二,schema.xml配置文件的代碼如下所示。
1 <?xml version="1.0"?>
2 <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
3 <mycat:schema xmlns:mycat="http://io.mycat/">
4 <schema name="redisDemo">
5 <table name="student" dataNode="dn1,dn2,dn3" rule="mod-long"/>
6 </schema>
7 <dataNode name="dn1" dataHost="host1" database="redisDemo" />
8 <dataNode name="dn2" dataHost="host2" database="redisDemo" />
9 <dataNode name="dn3" dataHost="host3" database="redisDemo" />
10 <dataHost name="host1" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
11 <heartbeat>select user()</heartbeat>
12 <writeHost host="hostM1" url="172.17.0.2:3306" user="root" password="123456"></writeHost>
13 </dataHost>
14 <dataHost name="host2" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
15 <heartbeat>select user()</heartbeat>
16 <writeHost host="hostM2" url="172.17.0.3:3306" user="root" password="123456"></writeHost>
17 </dataHost>
18 <dataHost name="host3" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
19 <heartbeat>select user()</heartbeat>
20 <writeHost host="hostM3" url="172.17.0.4:3306" user="root" password="123456"></writeHost>
21 </dataHost>
22 </mycat:schema>
在第4行到第6行裏,定義了redisDemo數據庫裏的student表,將按照mod-long規則,分佈到dn1,dn2,dn3這三個數據庫節點上。隨後在第7行到第9行的代碼裏,給出了dn1,dn2,dn3這三個節點的定義,它們分別指向host1,host2和host3的redisDemo數據庫。
在第10行到第21行的代碼裏,給出了針對host1到host3的定義,它們的配置很相似,這裏就以第10行到第13行的host1配置來說明。
在第10裏,首先通過dbType參數,定義了host1是mysql類型的數據庫,隨後通過maxCon和minCon參數指定了該host數據庫的最大和最小連接數,通過balance和writeType參數,指定了向host1讀寫的請求,其實是發送到第12行定義的,url是172.17.0.2:3306的mysql數據庫,同時在第12行裏,還指定了連到172.17.0.2:3306的mysql數據庫的用戶名和密碼。在第11行定義的heartbeat參數,則定義了MyCAT組件用select user()這句sql語句來判斷host1這個數據庫能否處於“連接”狀態。也就是說,在第5行定義的dn1節點,最終是指向172.17.0.2:3306所在的MySQL數據庫的stduent表。
類似的在第14行到第21行鍼對host2和host3的定義裏,分別也定義裏這兩個數據庫的具體url地址。也就是說,定義在第4行的redisDemo數據庫裏的student表,根據dataNode的定義,最終會分散到172.17.0.2:3306、172.17.0.3:3306和172.17.0.4:3306這三個redisDemo數據庫裏的stduent表裏。
通過下圖,大家能更清晰地看到通過配置文件裏相關參數定義的分庫關係。
在本範例中,是用Docker容器在同一臺主機裏創建三個MySQL實例,所以172.17.0.2:3306、172.17.0.3:3306和172.17.0.4:3306是本機三個Docker容器的地址。如果在項目裏,是在多臺主機上部署MySQL服務器,那麼對應的地址就應該修改成這些主機的IP地址。
第三,rule.xml配置文件的代碼如下所示。
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mycat:rule SYSTEM "rule.dtd">
3 <mycat:rule xmlns:mycat="http://io.mycat/">
4 <tableRule name="mod-long">
5 <rule>
6 <columns>id</columns>
7 <algorithm>mod-long</algorithm>
8 </rule>
9 </tableRule>
10
11 <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
12 <property name="count">3</property>
13 </function>
14 </mycat:rule>
在第4行裏定義了mod-long這個規則,該規則在schema.xml第5行裏被用到,再結合第11行到第13行的代碼,能看到利用該規則對student表分庫時,將先對id進行模3處理,然後再根據取模後的結果,到host1到host3所在的數據表的student庫裏進行處理。這裏取模的數值3,是需要和MySQL主機的數量相同。
上述三個配置文件綜合起來,給出瞭如下針對分庫分表相關動作的定義。
1. 應用程序如果如果要使用MyCAT,需要用root用戶名外帶123456密碼連接到該MyCAT組件。
2. 比如要插入id爲1的stduent數據,根據在schema.xml裏的定義,會先根據mod-long規則,對id進行模3處理,結果是1,所以會插入到host2所定義的172.17.0.3:3306數據庫的student表裏,如果要進行讀取、刪除和更新操作,也會先對id模3,然後再把該請求發送到對應的數據庫裏。
這裏僅給出了MyCAT分庫的一種比較常用的規則(即取模),也只是把stduent表分散到3個物理數據表裏,事實上通過編寫配置,可以用其它算法,讓MyCAT組件把數據表分散到更多的子表裏。
3 Java、MySQL與MyCAT的整合範例
這裏將以“一個MyCAT組件連接三個MySQL數據庫,對student表進行分庫”的需求爲例,結合上文給出的MyCAT三個配置文件,給出基於Docker容器設置MyCAT分庫分表的詳細步驟,並在此基礎上,給出Java應用程序連接MyCAT以實現分庫分表的代碼範例。
步驟一,先通過如下3個Docker命令,準備3個包含MySQL的Docker容器。
1 docker run -itd -p 3306:3306 --name mysqlHost1 -e MYSQL_ROOT_PASSWORD=123456 mysql:latest
2 docker run -itd -p 3316:3306 --name mysqlHost2 -e MYSQL_ROOT_PASSWORD=123456 mysql:latest
3 docker run -itd -p 3326:3306 --name mysqlHost3 -e MYSQL_ROOT_PASSWORD=123456 mysql:latest
這裏創建的三個MySQL的docker容器分別叫mysqlHost1、mysqlHost2和mysqlHost3,在容器裏它們都工作在3306端口,但它們分別映射到主機的3306、3316和3326端口。並且,通過-e參數,分別指定了這三個數據庫root用戶名的密碼是123456。
創建完成後,再分別通過如下的命令,觀察它們所在docker容器的ip地址。
1 docker inspect mysqlHost1
2 docker inspect mysqlHost2
3 docker inspect mysqlHost3
觀察到的IP地址如下表所示,大家在自己電腦上操作時,如果看到的是其它的IP地址,就需要更改下文步驟裏的相關配置項。
步驟二,通過docker exec -it mysqlHost1 /bin/bash命令進入到mysqlHost1容器,隨後再用mysql -u root -p命令進入到mysql數據庫,進入時需要輸入的密碼是123456,隨後運行如下的命令創建redisDemo數據庫和student表。
1 create database redisDemo;
2 use redisDemo;
3 create table student( id int not null primary key,name char(20),age int,score float);
其中第1行語句是用於建庫,第2行語句是進入redisDemo庫,第3行語句是用於建表。
完成後,通過docker exec -it mysqlHost2 /bin/bash和docker exec -it mysqlHost3 /bin/bash這兩條命令進入到另外兩個MySQL容器裏,也通過mysql命令進入到數據庫,也再通過上述語句進行創建數據庫和數據表的動作。至 此完成了針對三個MySQL數據庫的創建動作。
步驟三,通過docker pull命令下載mycat組件的鏡像,如果無法下載,則可以通過docker search mycat命令尋找可用的鏡像並下載。
步驟四,新建C:\work\mycat\conf目錄,在其中放入在10.2.2部分裏給出的針對MyCAT組件的server.xml、rule.xml和schema.xml這三個配置文件。
其中在schema.xml裏,針對數據庫url的定義如下第3行、第7行和第11所示。請注意它們指向的是具體Docker容器裏的MySQL的IP地址,它們的值需要和表10.3裏給出的值一致。如果大家用docker inspect命令觀察到三個Docker的地址有變,就需要對應第修改schema.xml裏的url值。
1 <dataHost name="host1" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
2 <heartbeat>select user()</heartbeat>
3 <writeHost host="hostM1" url="172.17.0.2:3306" user="root" password="123456"></writeHost>
4 </dataHost>
5 <dataHost name="host2" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
6 <heartbeat>select user()</heartbeat>
7 <writeHost host="hostM2" url="172.17.0.3:3306" user="root" password="123456"></writeHost>
8 </dataHost>
9 <dataHost name="host3" dbType="mysql" maxCon="10" minCon="3" balance="0" writeType="0" dbDriver="native">
10 <heartbeat>select user()</heartbeat>
11 <writeHost host="hostM3" url="172.17.0.4:3306" user="root" password="123456"></writeHost>
12 </dataHost>
步驟五,再確保上述三個Docker裏包含的My SQL都處於可用狀態後,通過如下的Docker命令啓動MyCAT對應的docker容器。
1 docker run --name mycat -p 8066:8066 -p 9066:9066 -v C:\work\mycat\conf\server.xml:/opt/mycat/conf/server.xml:ro -v C:\work\mycat\conf\schema.xml:/opt/mycat/conf/schema.xml:ro -v C:\work\mycat\conf\rule.xml:/opt/mycat/conf/rule.xml:ro -d mycat:latest
請注意該docker命令的如下要點。
1 通過-p參數,把該MyCAT組件的工作端口8066和管理端口9066映射到主機裏的同名端口。
2 通過三個-v參數,把容器外C:\work\mycat\conf\目錄裏的三個MyCAT配置文件映射到容器內的/opt/mycat/conf/目錄裏,這樣啓動時,就能讀到這三個配置文件。這樣做的前提是,事先已經確認過容器內的server.xml等三個配置文件存在於/opt/mycat/conf/目錄裏,如果有些mycat鏡像裏的這三個配置文件不存在於這個目錄,則可以先用docker exec -it mycat /bin/bash命令進入該mycat容器,找到這三個配置文件對應的位置後,再改寫上述啓動mycat容器的docker run命令。
3 通過mycat:latest參數指定該容器是基於mycat:latest鏡像生成的。
運行完上述docker run命令後,可以通過docker logs mycat命令觀察包含在該容器內的MyCAT組件的啓動日誌。如果成功啓動,就能看到日誌裏有如下圖10.12所示的提示成功的信息。如果有錯誤,那麼或者去檢查三個MySQL數據庫的連接狀態,或者根據日誌裏給出的錯誤提示來排查問題。
至此完成了MyCAT組件和三個MySQL數據庫的相關配置,在如下的MyCATSimpleDemo範例中,將給出Java程序通過MyCAT組件向MySQL數據庫插入數據的做法,從中大家能感受到分庫分表的效果。
1 import java.sql.*;
2 public class MyCATSimpleDemo {
3 public static void main(String[] args){
4 //定義連接對象和PreparedStatement對象
5 Connection myCATConn = null;
6 PreparedStatement ps = null;
7 //定義連接信息
8 String mySQLDriver = "com.mysql.jdbc.Driver";
9 String myCATUrl = "jdbc:mysql://localhost:8066/redisDemo";
10 String user = "root";
11 String pwd = "123456";
12 try{
13 Class.forName(mySQLDriver);
14 myCATConn = DriverManager.getConnection(myCATUrl, user, pwd);
15 ps = myCATConn.prepareStatement("insert into student (id,name,age,score) values (?,'test',18,100)");
16 ps.setString(1,"11");
17 ps.addBatch();
18 ps.setString(1,"12");
19 ps.addBatch();
20 ps.setString(1,"13");
21 ps.addBatch();
22 ps.executeBatch();
23 } catch (SQLException se) {
24 se.printStackTrace();
25 } catch (Exception e) {
26 e.printStackTrace();
27 }
28 finally{
29 //如果有必要,釋放資源
30 if(ps != null){
31 try {
32 ps.close();
33 } catch (SQLException e) {
34 e.printStackTrace();
35 }
36 }
37 if(myCATConn != null){
38 try {
39 myCATConn.close();
40 } catch (SQLException e) {
41 e.printStackTrace();
42 }
43 }
44 }
45 }
46 }
在本範例的第14行裏,創建了指向MyCAT組件的連接對象myCATConn,請注意它是指向localhost的8066端口,用root和123456連接到redisDemo數據庫,這和在server.xml裏的配置相吻合。在隨後的第15行裏,是用myCATConn創建PreparedStatement類型的ps對象,並在第16行到第21行的代碼裏,通過addBatch方法批量組裝了三條insert語句,請注意它們的id分別是11、12和13,最後在第22行的代碼裏,通過executeBatch語句執行了這三條insert語句。
從中大家可以看到,通過MyCAT連接對象執行SQL語句的方式和直接用MySQL連接對象的方式基本相同,而且在獲取MyCAT連接對象時,只需要對應地更改連接url即可。也就是說,MyCAT組件在實現分庫分表時,對應用程序來說是透明的,它完全分離了“數據操作的業務動作”和“數據操作的底層實現”,所以如果要在一個系統裏引入MyCAT分庫分表組件,修改的點非常有限,對原有業務的影響並不大。
再來看下分庫分表的效果,通過docker exec -it mysqlHost1 /bin/bash命令進入到mysqlHost1容器,隨後再用mysql -u root -p命令進入到mysql數據庫,用use redisDemo;命令進入到redisDemo數據庫後,執行select * from student;命令,只能看到一條數據,如下圖所示。
同樣地,在mysqlHost2和mysqlHost3所在的數據庫裏,也只能看到一條數據,這三個數據庫裏存儲的student數據如下表所示。
從中大家可以看到,根據id模3取值的不同,MyCAT組件分別把它們分散到了3個數據庫裏。由於本書的重點是Redis,所以就不再給出用MyCAT組件進行刪除、更新和查詢操作的相關範例,不過如果大家用上述範例中的myCATConn連接對象以及用它生成的ps對象,實現相關操作的效果也不難。
這裏student表中的數據規模很小,其實無法體現出分庫分表的優勢,但如果這張表的規模很大,比如達到百萬級甚至更高,那麼通過MyCat組件引入分庫分表效果後,就相當於把針對這張大表的壓力均攤到了若干張子表上,就能更好地應對高併發的場景。