1. 程式人生 > >Redis整合MySQL和MyCAT分庫元件(來源是我的新書)

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

 

    

&n