1. 程式人生 > >【MyCat】通過mycat實現mysql資料庫的分庫分表及sql防火牆配置

【MyCat】通過mycat實現mysql資料庫的分庫分表及sql防火牆配置

前言:隨著業務的不斷髮展,不論你怎麼優化程式碼和負載均衡,都不得不面對資料庫效能的瓶頸,為了讓資料庫的效能得到極大改善,除了優化Mysql本身的配置,以及SQL語句和索引等優化,更重要的就是對現有資料庫進行合理拆分,然後分佈在不同的伺服器上,以減輕單個伺服器的IO壓力,本篇就跟大家一起分享一下如何使用MyCat實現對資料庫表的垂直拆分和水平拆分.關於MyCat我再簡單囉嗦幾句,MyCat是基於阿里的開源中介軟體cobar開發的一款資料庫中介軟體,由於其前身cobar是有過實際千萬級資料併發量測試的,所以其效能是十分可靠的,加上是阿里爸爸開發的,所以就不要問太多,學就是了.

一.資料庫表的垂直拆分.

1.什麼是垂直拆分?

所謂垂直拆分就是指把原來一個庫中的多個表,按照一定的拆分邏輯將他們拆分出來,放到不同伺服器的不同庫中.比如開發了一個類似淘寶的電商平臺,一開始是把所有表都放在同一個庫中,現在可以把使用者模組,商品模組,訂單模組相關的表各自拆分到不同的庫中.

2.垂直拆分的優缺點?

優點:通過垂直拆分,可以使得業務關係更清晰,同時可以降低單臺伺服器的IO壓力,提高資料庫效能.

缺點:拆分後會使得一些聯表查詢變得困難,好在較新版本的MyCat提供了全域性表功能可以較好的解決這個問題.

3.如何進行垂直拆分?

首先你必須清楚現有業務,弄清楚效能瓶頸主要出現在哪幾張表中,另外要確定一下如何拆分可以讓不同模組之間的關聯最小,同樣遵循低耦合,高內聚.讓拆分後的表所屬模組儘量清晰明朗,更為優雅,就像拆分訂單模組商品模組這樣...

確定好了拆分規則以後,下面我們就一起來實際操作一把.

這裡我以大家熟悉的電商平臺為例,因為拆分規則比較成熟,大家只需要關心如何配置即可.

先看一下我要拆分的資料庫:

再來看一下拆分後的資料庫:

   

就是把imooc_db(總庫)拆分成了product_db(產品庫),order_db(訂單庫),customer_db(使用者庫)三個庫,分別存放了相關的表.

Mycat關聯後的邏輯庫:

拆分後通過MyCat建立一個邏輯庫,關聯到這三個庫,該邏輯庫相當於是這三個庫的檢視或者聯合,這樣對後端應用而言就是無感知的,MyCat遮蔽了分庫後的差異,對後端應用而言連線的資料庫跟原來直接連imooc_db總庫的效果是一樣的,但實際上是連線到了三臺不同伺服器上的不同庫裡.

垂直拆分可以通過如下五個步驟來實現:

第一步:事實上第一步我已經在上面提到了,就是把一個庫拆成訂單,商品和使用者三個庫.

第二步:複製資料庫到其它伺服器中,我這裡準備了四臺伺服器,一臺是一開始未拆分資料庫的伺服器,其它三臺伺服器用來存放拆分後的資料庫.

這裡需要一些MySQL主從複製的基礎,如果不會的話可以看我上一篇,MySQL主從複製

我預設大家都會MySQL主從複製,需要將第一臺伺服器上的表imooc_db分別複製到另外三臺伺服器上的product_db,order_db,customer_db上,由於上篇已經大篇幅講過主從複製了,這裡我就簡單寫一下.

#在第一臺伺服器上建立使用者並授權.
create user 'laohan'@'119.1.2.%' identified by 'Laohan0.0';
grant replication slave on *.* to [email protected]'119.1.2.%';

#使用命令匯出Mysql日誌.
mysqldump --single-transaction --master-data=2 --trigger --routines --all-databases -uroot -p >bakup_imooc_db.sql

#使用scp命令將匯出的日誌複製到其它3臺伺服器上
scp bakup_imooc_db.sql 'root'@'119.1.2.2':/home
scp bakup_imooc_db.sql 'root'@'119.1.2.3':/home
scp bakup_imooc_db.sql 'root'@'119.1.2.4':/home

#在另外三臺伺服器上分別建立對應的資料庫.
mysql -uroot -p -e"create database product_db"
mysql -uroot -p -e"create database order_db"
mysql -uroot -p -e"create database customer_db"

#在另外三臺伺服器上依次使用命令將日誌恢復到伺服器資料庫中,完成資料匯入工作.
mysql -uroot -p product_db < bakup_imooc_db.sql
mysql -uroot -p order_db < bakup_imooc_db.sql
mysql -uroot -p customer_db < bakup_imooc_db.sql

#在另外三臺伺服器上依次使用命令配置主從複製
change master to master_host='119.1.2.2',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;
change master to master_host='119.1.2.3',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;
change master to master_host='119.1.2.4',master_user='laohan',master_password='Laohan0.0',master_log_file='mysql-bin.000004',master_log_pos=1687;

#在另外三臺伺服器上依次使用命令更改主從複製庫名不一致的問題
change replication filter replicate_rewrite_db=((imooc_db,product_db));
change replication filter replicate_rewrite_db=((imooc_db,order_db));
change replication filter replicate_rewrite_db=((imooc_db,customer_db));

#分別啟動三臺伺服器的主從複製
start slave;

#通過命令觀察三臺伺服器的IO和SQL程序是否都為YES
show slave status \G;
如果都為YES,說明主從複製配置成功.

第三步:配置MyCat垂直分庫規則,因為這裡暫時不做水平分庫,所以只需要配置第一臺伺服器MyCat 的server.xml和schema.xml,不需要關心rule.xml

配置server.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
        <!--system標籤內的內容可以直接copy過去,沒啥好說的,感興趣可以在mycat官網去研究配置-->
        <system>
          <property name="serverPort">8066</property>
          <property name="managerPort">9066</property>
          <property name="nonePasswordLogin">0</property>
          <property name="bindIp">0.0.0.0</property>
          <property name="frontWriteQueueSize">2048</property>

          <property name="charset">utf8</property>
          <property name="txIsolation">2</property>
          <property name="processors">8</property>
          <property name="idleTimeout">1800000</property>
          <property name="sqlExecuteTimeout">300</property>
          <property name="useSqlStat">0</property>
          <property name="useGlobleTableCheck">0</property>
          <property name="sequnceHandlerType">2</property>
          <property name="defaultMaxLimit">100</property>
          <property name="maxPacketSize">104857600</property>
        </system>
        <!--重點,使用者名稱,也就是到時候你的應用要連線的邏輯資料庫的使用者名稱和密碼-->
        <!--建議跟原來拆分前的資料庫使用者名稱和密碼保持一致,這樣對後端來說修改最少-->
        <user name="app_laohan">
                <!--這行表示使用對明文密碼加密-->
                <property name="usingDecrypt">1</property>
                <!--這行一串就是加密後的密碼,加密需要通過下面命令去生成下面這串密碼-->
                <!--進入mycat的lib目錄下,使用java -cp Mycat-server-版本號(可用tab鍵填充).jar io.mycat.DecryptUtil 0:賬號:密碼-->
                <!--其中0的意思是前端加密,輸完後敲回車就會生成下面這串密文-->
                <property name="password">BcuSAASJ9jyuQnyAOGHPKwIbB8YHlgLANvZe49RtFh0Wip66+jMZRDk5piVhp37EjF/xlEW6YvpbuBr3nN0EQw==</property>
                <property name="schemas">imooc_db</property>
        </user>

</mycat:server>

配置schema.xml:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
        <!--然後配置這塊,指明每張表對應的伺服器,並把相應的表歸屬於相應的資料節點,節點名可以自己取,比如ordb-->
        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <table name="order_master" primaryKey="order_id" dataNode="ordb" />
                <table name="order_detail" primaryKey="order_detail_id" dataNode="ordb" />
                <table name="order_customer_addr" primaryKey="customer_addr_id" dataNode="ordb" />
                <table name="order_cart" primaryKey="cart_id" dataNode="ordb" />
                <!--這裡region_info是全域性表,所以配置略有不同,它歸屬於所有資料節點,用逗號隔開,type為global-->
                <table name="region_info" primaryKey="region_id" dataNode="ordb,prodb,custdb" type="global" />
                <table name="shipping_info" primaryKey="ship_id" dataNode="ordb" />
                <table name="warehouse_info" primaryKey="w_id" dataNode="ordb" />
                <table name="warehouse_proudct" primaryKey="wp_id" dataNode="ordb" />

                <table name="product_brand_info" primaryKey="brand_id" dataNode="prodb" />
                <table name="product_category" primaryKey="category_id" dataNode="prodb" />
                <table name="product_comment" primaryKey="comment_id" dataNode="prodb" />
                <table name="product_info" primaryKey="product_id" dataNode="prodb" />
                <table name="product_pic_info" primaryKey="product_pic_id" dataNode="prodb" />
                <table name="product_supplier_info" primaryKey="supplier_id" dataNode="prodb" />

                <table name="customer_balance_log" primaryKey="balance_id" dataNode="custdb" />
                <table name="customer_inf" primaryKey="customer_inf_id" dataNode="custdb" />
                <table name="customer_login" primaryKey="customer_id" dataNode="custdb" />
                <table name="customer_login_log" primaryKey="login_id" dataNode="custdb" />
                <table name="customer_point_log" primaryKey="point_id" dataNode="custdb" />
        </schema>
        <!--最後配置這塊,指明每個資料節點對應的伺服器名,以及該資料節點對應的實際資料庫名-->
        <dataNode name="ordb" dataHost="mysql0132" database="order_db" />
        <dataNode name="prodb" dataHost="mysql0133" database="product_db" />
        <dataNode name="custdb" dataHost="mysql0134" database="customer_db" />

        <!--先配置這塊,指明拆分後的三個庫對應的伺服器Ip等資訊,至於writType之類的標籤含義需自參閱官網文件:http://www.mycat.io/-->
        <dataHost name="mysql0132" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.2" url="119.1.2.2:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0133" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.3" url="119.1.2.3:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0134" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.4" url="119.1.2.4:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

</mycat:schema>

第四步:通過MyCat訪問DB,前面配置好了之後,就啟動mycat觀察是否配置成功:

#啟動MyCat
mycat start

#觀察是否啟動成功(在Mycat的logs目錄下)
tail -f wrapper.log
如果看到:
Mycat server starup successfully.
說明啟動成功了.

#然後可以在另外隨便哪臺伺服器上連線到Mycat去訪問DB看看(賬號和密碼為server.xml配置的)

mysql -uapp_laohan -pLaohan0.0 -P8066 -h119.1.2.1

連線進去隨便執行幾條查詢看看是否成功.

第五步:如果第四步訪問和查詢都沒問題,那現在可以停掉三臺伺服器的主從複製,並刪除多餘的表

#停止其它三臺伺服器上的主從複製,在三臺伺服器上分別執行
stop slave;
reset slave all;

#用Navicat工具或者命令列刪除多餘的表,比如order_db庫中當前是包含imooc_db中的所有表的,現在只需要保留拆分後的表,也就是跟oredr相關的表,其它無關表都可以刪除掉了,值得注意的是需要保留一些全域性的表,比如字典表,這些表在涉及到聯表查詢時會用到,除了留下它們外還要對它們進行配置,這些全域性表的配置我在schema.xml中已經指明瞭如何配置了,這裡不再贅述.

至此,大功告成,你可以通過在server.xml中配置的賬號和密碼,通過埠號8066輕鬆連線到你配置好的邏輯庫中,該邏輯庫看上去跟原來未拆分的庫一模一樣,但實際上效能已經有很大飛躍,同時因為全域性表的出現,也不會影響你的聯表查詢.你甚至可以將mycat的埠號也設為3306,賬號密碼以及邏輯庫的名字都與未拆分前保持一致,這樣就可以讓後端應用無感知的就切換到了Mycat垂直拆分後的資料庫上.

二.資料庫表的水平拆分

1.什麼是資料庫表的水平拆分?

水平拆分是在垂直拆分的基礎上進一步對現有壓力過大的資料庫進行橫向拆分,比如原來的訂單庫在生產環境中壓力過大,現在把該訂單庫根據一定的規則拆分成了4個訂單庫,每個訂單庫分別儲存一部分資料,以減輕IO壓力.

2.水平拆分的優缺點

優點:通過水平拆分後可以減少單個數據庫中儲存資料過多的情況,在查詢中可以顯著提高效率,同時也可以減輕資料庫壓力.

缺點:拆分後涉及到一些聯表查詢會因為資料存在分片不一致而無法完成查詢.好在MyCat可以通過配置ER分片來解決此問題.

3.如何進行水平拆分

首先,水平拆分需要注意下面幾個原則:

水平拆分主要分為以下幾個步驟:

1.根據業務狀態,我們發現訂單這個模組的資料量比較大,資料庫壓力過大,需要進行水平拆分,這裡挑選出order_master這張表進行水平切分.

2.根據選擇分片鍵的原則:

觀察order_master這張表裡面的欄位後發現,最終選擇customer_id的值作為分片鍵,由於該鍵是自增的整型數值,所以選擇簡單取模分片演算法比較合適.

3.在確定好了要拆分的表以及分片鍵和分片演算法後,下面進入實操部分.

首先配置在第二臺和第三臺伺服器上建立orderdb01-04這些資料庫,並匯入order_master表的結構.

修改schema.xml配置:

由於我依舊使用前面垂直拆分後的配置檔案,所以在原來的基礎上,我所水平拆分的庫依舊分佈在第二臺和第三臺伺服器上,故無需配置dataHost.根據下面配置中的註釋順序修改部分配置即可.

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <!--然後修改這裡需要拆分的order_master表所對應的資料節點,用逗號隔開即可,分片規則暫時命名為order_master-->
                <table name="order_master" primaryKey="order_id" dataNode="orderdb01,orderdb02,orderdb03,orderdb04" rule="order_master" />
                <table name="order_detail" primaryKey="order_detail_id" dataNode="ordb" />
                <table name="order_customer_addr" primaryKey="customer_addr_id" dataNode="ordb" />
                <table name="order_cart" primaryKey="cart_id" dataNode="ordb" />
                <table name="region_info" primaryKey="region_id" dataNode="ordb,prodb,custdb" type="global" />
                <table name="shipping_info" primaryKey="ship_id" dataNode="ordb" />
                <table name="warehouse_info" primaryKey="w_id" dataNode="ordb" />
                <table name="warehouse_proudct" primaryKey="wp_id" dataNode="ordb" />

                <table name="product_brand_info" primaryKey="brand_id" dataNode="prodb" />
                <table name="product_category" primaryKey="category_id" dataNode="prodb" />
                <table name="product_comment" primaryKey="comment_id" dataNode="prodb" />
                <table name="product_info" primaryKey="product_id" dataNode="prodb" />
                <table name="product_pic_info" primaryKey="product_pic_id" dataNode="prodb" />
                <table name="product_supplier_info" primaryKey="supplier_id" dataNode="prodb" />

                <table name="customer_balance_log" primaryKey="balance_id" dataNode="custdb" />
                <table name="customer_inf" primaryKey="customer_inf_id" dataNode="custdb" />
                <table name="customer_login" primaryKey="customer_id" dataNode="custdb" />
                <table name="customer_login_log" primaryKey="login_id" dataNode="custdb" />
                <table name="customer_point_log" primaryKey="point_id" dataNode="custdb" />

        </schema>

        <dataNode name="ordb" dataHost="mysql0132" database="order_db" />
        <dataNode name="prodb" dataHost="mysql0133" database="product_db" />
        <dataNode name="custdb" dataHost="mysql0134" database="customer_db" />
        <!--首先配置orderdb01-04所分佈的資料節點-->
        <dataNode name="orderdb01" dataHost="mysql0132" database="orderdb01" />
        <dataNode name="orderdb02" dataHost="mysql0132" database="orderdb02" />
        <dataNode name="orderdb03" dataHost="mysql0133" database="orderdb03" />
        <dataNode name="orderdb04" dataHost="mysql0133" database="orderdb04" />


        <dataHost name="mysql0132" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.2" url="119.1.2.2:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0133" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.3" url="119.1.2.3:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

        <dataHost name="mysql0134" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1">
                <heartbeat>select user() </heartbeat>
                <writeHost host="119.1.2.4" url="119.1.2.4:3306" user="dba" password="Woaiyinyin0.0" />
        </dataHost>

</mycat:schema>

修改完成後儲存,接下來配置rule.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
        <!--name要與schema中配置的name一致-->
        <tableRule name="order_master">
                <rule>
                        <!--這裡即前面選出的分片鍵-->
                        <columns>customer_id</columns>
                        <!--指明分片演算法為簡單取模演算法-->
                        <algorithm>mod-long</algorithm>
                </rule>
        </tableRule>
        <!--定義分片函式:簡單取模演算法,指明它所在的包名-->
        <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
                <!--指明所要分片的數量,這裡分了4個庫,所以寫4-->
                <property name="count">4</property>
        </function>

</mycat:rule>

修改完成後儲存並重啟Mycat,重啟後觀察日誌是否重啟成功,如果成功則可進入測試階段:

連線Mycat資料庫:


mysql -uapp_laohan -pLaohan0.0 -P8066 -h119.1.2.1

然後向Mycat的邏輯庫Imooc_db中的order_master中插入幾條資料,然後進入orderdb01-04中觀察分佈情況,如果分佈ok,那說明已經配置成功.(建議使用Navicat這類視覺化工具進行操作會更直觀)

至此尚未大功告成,因為你會發現經過水平拆分後,自增的order_id不是唯一的了,有重複的order_id出現,因此你還需要配置全域性的自增ID,MyCat為你提供了配置全域性ID的幾種方式,由於MyCat也需要做高可用,所以我這裡就不講傳統的配置方式了,感興趣的到下篇去看zookeeper方式的全域性ID配置.

前面提到了ER分片也講一下,由於分片將order_master表中的資料分佈在了不同的庫中,當需其他表要Jion表order_master查詢時可能會出現查詢錯誤的情況,為了解決該問題就需要配ER分片.

場景:

◆適合一些資料比較多的大表.如果資料量不多的情況下使用垂直拆分時提到的全域性表策略即可解決.

◆適合跟水平拆分表order_master關聯查詢比較多的表.

首先要找到需要聯表查詢比較多的表,然後將該表也做分片,而且配置時要把order_master當爸爸...

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

        <schema name="imooc_db" checkSQLschema="false" sqlMaxLimit="100">
                <!--配置childTable,指明JionKey和ParentKey-->
                <table name="order_master" primaryKey="order_id" dataNode="orderdb01,orderdb02,orderdb03,orderdb04" rule="order_master" >
                    <childTable name=order_detail  primaryKey="order_detail_id" joinKey="order_id" parentKey="order_id"/>
                </table>
<!--後面的配置就省略了,跟上面的一樣-->

</mycat:schema>

然後重啟Mycat,成功後分別向order_master和order_detail表中插入多條order_id相同的資料,然後通過聯表查詢測試是否成功.

文末,送點選單,MyCat的SQL攔截和防火牆配置:

需要配置server.xml:

下回分解:如何搭建真正高可用的企業級mycat.