Redis整合MySQL和MyCAT分庫組件

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組件引入分庫分表效果後,就相當於把針對這張大表的壓力均攤到了若干張子表上,就能更好地應對高併發的場景。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章