MyCat 讀寫分離與分庫分表

1、Mycat 簡介

Mycat 是一個開源的數據庫中間件,能夠解決分佈式數據庫環境下的大多數問題,如讀寫分離、分庫分表等,除此以外,它還具有如下特性:php

  • 支持 MySQL、Oracle、DB二、SQL Server、PostgreSQL 等數據庫常見的 SQL 語法。
  • 可以基於心跳機制進行自動地故障切換,支持讀寫分離,支持MySQL主從架構,以及 galera cluster 集羣。
  • 支持數據的多片自動路由與聚合,支持 sum,count,max 等經常使用的聚合函數,支持跨庫分頁。
  • 支持單庫內部任意 join,支持跨庫兩表 join,也支持基於 caltlet 的多表 join。
  • 支持經過全局表,ER 關係表等分片策略來實現多表 join 查詢。
  • 支持XA分佈式事務(1.6.5 +)。
  • 支持全局序列號,可以解決分佈式環境下的主鍵生成問題。
  • 支持使用 zookeeper 來協調主從切換、統一管理配置數據以及使用 zookeeper 來生成全局惟一 ID。( 1.6+ )
  • 提供了 Web 監控界面,來對 Mycat、MySQL 以及服務器的狀態進行監控。

2、Mycat 核心概念

在引入 Mycat 後,全部的客戶端請求須要通過中間件進行轉發上,此時客戶端直接面向的是 Mycat 上的邏輯庫或邏輯表:java

邏輯庫

在 Mycat 的配置文件中進行定義,它對應一個或者多個實際的數據庫或數據庫集羣。node

邏輯表

能夠對應一張實際的表,也能夠表示爲多個分片表的集合。按其特性能夠分爲如下四類:mysql

  • 分片表:表按照指定鍵進行拆分,並散落到多個數據庫實例上,從而能夠解決單表過大的問題。
  • 非分片表:一般狀況下,只有大表須要分片,小表並無分片的必要,因此在 Mycat 中它們也被稱爲非分片表,主要是爲了在概念上與分片表進行區分。
  • ER表:基於實體關係模型進行分片的表,如訂單表和訂單明細表一般都是大表,此時能夠按照訂單號進行 ER 分片,從而保證同一單號的訂單記錄和明細記錄都處於同一分片上,進而避免跨分片查詢。
  • 全局表:一樣也是用於避免跨分片查詢。假設在查詢訂單明細時須要查詢產品所屬分類 (如家電產品、生活用品),產品類別表一般是小表,此時能夠聲明爲全局表,以後 Mycat 會將其拷貝到全部分片上,從而避免跨分片查詢。

分片節點

將表按照分片鍵進行分片後,一個表中的全部數據就會被分發到不一樣的數據庫上,這些數據庫節點就稱爲分片節點。git

3、Mycat 安裝

4、Mycat 基本配置

在 Mycat 的安裝目錄的 conf 目錄下,有如下三個核心配置文件:github

  • server.xml:主要用於定義用戶信息,全局 SQL 防火牆、以及與 Mycat 自身相關的配置。
  • schema.xml:主要用於定義邏輯庫,邏輯表,邏輯數據節點以及實際的物理數據庫的信息。
  • rule.xml:主要用於定義分片規則,並提供給 schema.xml 標籤中的分片表使用。

4.1 server.xml

一個 server.xml 的配置示例以下,主要包含如下標籤:算法

  • <system> :定義 Mycat 相關的配置。
  • <firewall>:配置全局 SQL 防火牆,能夠在其下經過子標籤訂義 IP 白名單和黑名單。
  • <user>:用於設置默認帳戶或普通帳戶,併爲其分配在邏輯庫或邏輯表上的權限。權限經過 dml 屬性來指定,如 dml="0110" ,四個位置上的數值分別對應 insert,update,select,delete 權限,1 爲具備該權限,0 爲不具備。
<system> 
    <!-- 0爲須要密碼登錄、1爲不須要密碼登錄 ,默認爲0,設置爲1則須要指定默認帳戶-->
    <property name="nonePasswordLogin">0</property> 
    <property name="charset">utf8</property>
</system>

<firewall>
    <whitehost>
        <host host="1*7.0.0.*" user="root"/>
    </whitehost>
    <blacklist check="false">
    </blacklist>
</firewall>

<!-- 定義默認帳戶-->
<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">TESTDB</property>
    <!-- 權限設置 -->
    <privileges check="true">
        <schema name="TESTDB" dml="0110" >
            <table name="tb01" dml="0000"></table>
            <table name="tb02" dml="1111"></table>
        </schema>
    </privileges>
</user>

<!-- 定義默認帳戶-->
<user name="user">
    <property name="password">123456</property>
    <property name="schemas">TESTDB</property>
    <property name="readOnly">true</property>
</user>
複製代碼

在上面的示例中,用戶配置的是明文密碼,這樣會存在安全隱患,所以 Mycat 也支持對密碼進行加密,示例以下:sql

# 該jar包在Mycat安裝目錄的lib目錄下
shell > java -cp Mycat-server-1.6.7.1-release.jar  io.mycat.util.DecryptUtil 0:root:123456
GO0bnFVWrAuFgr1JMuMZkvfDNyTpoiGU7n/Wlsa151CirHQnANVk3NzE3FErx8v6pAcO0ctX3xFecmSr+976QA==
複製代碼

能夠將明文密碼替換爲加密密碼,但此時還需在對應用戶的 user 標籤下增長以下配置,表明啓用加密功能:shell

<property name="usingDecrypt">1</property> 
複製代碼

4.2 schema.xml

下面是一個示例的 schema.xml 配置文件,主要包含如下標籤:數據庫

  • <schema>:主要用於定義邏輯庫,checkSQLschema 代表是否檢查 schema,當其爲 true 時,若是發送 select * from db.table1 等查詢時,會自動修改成 select * from table1 。sqlMaxLimit 用於限制返回數據的行數。
  • <table>:主要用於定義邏輯表。這裏以分片表爲例,定義了訂單表的分片節點及分片規則。
  • <dataNode>:定義邏輯數據節點。下面示例配置中的每一個節點分別對應主從複製集羣上的一個數據庫實例。
  • <dataHost>:實際的單臺物理數據庫或數據庫集羣,下面的示例配置中對應的是一個 maser-slave 集羣。
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
    <!-- mod-long 分片規則在下文的 rule.xml中定義 -->
    <table name="order" dataNode="dn1,dn2,dn3" rule="mod-long" />
</schema>

<dataNode name="dn1" dataHost="cluster01" database="order_db" />
<dataNode name="dn2" dataHost="cluster02" database="order_db" />
<dataNode name="dn3" dataHost="cluster03" database="order_db" />

<dataHost name="cluster01" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="native" switchType="1">
    <!-- 心跳檢查 -->
    <heartbeat>select user()</heartbeat>
    <writeHost host="master" url="192.168.0.226:3306" user="root" password="123456">
        <readHost host="salve" url="192.168.0.227:3306" user="root" password="123456" />
    </writeHost>
</dataHost>

...... 省略 cluster02,cluster03
複製代碼

這裏解釋一下 dataHost 標籤的相關屬性:maxCon 表示最大鏈接數,minCon 表示最小鏈接數,dbType 表示數據庫類型 ( 如 mysql、oracle 等),其餘屬性都有多個可選值,具體以下:

  • dbDriver:數據庫類型,可選的值有 native 和 JDBC,若是是 mysql,maridb,postgresql 等數據庫,直接可使用 native 便可。其餘數據庫則須要將對應的驅動包拷貝到 Mycat 安裝目錄的 lib 目錄下,並寫上完整的驅動類的類名。

  • switchType

    • 默認值爲 1,表明自動切換;
    • 設置爲 2,表明基於 MySQL 主從同步的狀態來決定是否切換;
    • 設置爲 3,表明基於 MySQL galary cluster 的切換機制進行切換;
    • 若是你的集羣基於 MMM 或 MHA 等高可用架構實現自動切換,則能夠將該值設置爲 -1,表明不切換。
  • balance

    • balance="0" :不開啓讀寫分離機制,全部讀請求都發送到當前可用的 writeHost 上。
    • balance="1" :所有的 readHost 與 stand by writeHost 參與讀操做。stand by writeHost 一般指的是雙主複製中處於 stand 狀態的主節點,即假設集羣複製架構爲 Master1 -> Slave1,Master2 -> Slave2,而且 M1 與 M2 互爲主備 ),此時 Master2 ,Slave1,Slave2 都會參與讀的負載。
    • balance="2" :全部讀請求隨機在 writeHost、 readhost 上進行分發。
    • balance="3" :全部讀請求隨機分發到 writeHost 對應的 readhost 執行,writerHost 不負擔讀壓力。

4.3 rule.xml

rule.xml 文件中定義的是分片規則,主要包含如下標籤:

  • <tableRule>:定義分片規則的名稱,分片鍵和分片算法。如下分片規則表示對訂單 Id 按照取模算法進行分片。
  • <function>:定義分片算法的具體實現類,以及所須要的參數。
<tableRule name="mod-long">
    <rule>
        <columns>order_id</columns>
        <algorithm>mod-long</algorithm>
    </rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
    <!-- nodes 節點的數量-->
    <property name="count">3</property>
</function>
複製代碼

Mycat 內置支持十幾種分片算法,如 取模分片,枚舉分片,範圍分片,字符串 hash 分片,一致性 hash 分片,日期分片等。關於這些分片算法的詳細說明能夠參考官方文檔:Mycat 官方指南

5、Mycat 讀寫分離

Mycat 讀寫分離的配置很是簡單,只須要經過配置 balance,writeHost 和 readHost 就能夠實現,示例以下:

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native">
    <heartbeat>select user()</heartbeat>
    <!-- 能夠配置多個writeHost -->
    <writeHost host="Master" url="hostname1:3306" user="root" password="123456">
        <!-- 能夠配置多個readHost-->
        <readHost host="Slave" url="hostname2:3306" user="root" password="123456" />
    </writeHost>
</dataHost>
複製代碼

可是須要注意的是如上的配置仍是會存在單點問題,由於只有一個 writeHost ,Mycat 支持配置多個 writeHost,示例以下:

<writeHost host="Master" url="hostname1:3306" user="root" password="123456">
    <!-- 能夠配置多個readHost-->
    <readHost host="Slave1" url="hostname2:3306" user="root" password="123456" />
    <readHost host="Slave2" url="hostname3:3306" user="root" password="123456" />
</writeHost>
<writeHost host="Slave3" url="hostname4:3306" user="root" password="123456" />
複製代碼

以上是 Mycat 官方指南中給出的配置,即在一主三從的複製架構下,能夠選擇其中一個 Slave 爲備用的寫入節點,此時當 Master 節點宕機後,會繼續在該備用節點執行寫入操做。這個配置和架構存在如下兩個問題:

  • 第一 Mycat 並不能讓 Slave 1 和 Slave 2 自動將本身的複製主節點變動爲 Slave 3,這個過程仍須要你本身來實現。
  • 第二你很難肯定哪一個節點該做爲備用的主節點,在上面的配置中咱們設置 Slave 3 爲備用節點,但在主節點宕機後,可能 Slave 1 和 Slave 2 的複製偏移量都要大於 Slave 3,顯然它們更適合成爲新的主節點。

基於以上兩個緣由,若是想要實現高可用,並不建議配置多個 writeHost ,而是配置一個 writeHost ,但其指向的是虛擬的讀 IP 地址,此時複製架構由 MMM 或者 MHA 架構來實現,並由它們來提供虛擬機的讀 IP。

6、Mycat 分庫分表

綜合以上所有內容,這裏給出一個分庫分表的示例,其架構以下:

如上圖所示,這裏模擬的是一個電商數據庫,並對其執行分庫分表操做:

  • 將用戶相關表,訂單相關表,商品相關表分表拆分到單獨的數據庫中;
  • 將訂單表和訂單明細表進行橫向分表,拆分到不一樣的數據庫中。同時爲了不訂單表關聯訂單明細表時出現跨分片查詢的狀況,須要將其配置爲 ER 表;
  • 因爲地址表,在查詢用戶信息(如家庭地址),訂單信息(收貨地址),商品信息(商品產地)的時候都須要用到,因此會將其聲明爲全局表,它會存在於以上全部分片上,從而避免跨分片查詢。

爲節省篇幅,以上全部測試表和測試庫的創建語句單獨整理至:ec_shop.sql 。分庫分表的具體操做以下:

6.1 server.xml

這裏新增一個 Mycat 用戶,並定義其管理的邏輯數據庫爲 ec_shop,另外使用 fakeMySQLVersion 來定義你所須要模擬的 MySQL 數據庫的版本。若是沒有特殊需求, Mycat 自帶的 server.xml 中的其餘配置可不作更改:

<system>
    <property name="fakeMySQLVersion">5.7.20</property>
</system>

<user name="heibaiying">
    <property name="password">
        B+BlA/U17pjyzHslglpDgYUxpgqK8qC62IRt/S74RBW6R7dZFJAXVb5tJDgmhzM4fj14MMhLnNmvKko6D73+iA==
    </property>
    <property name="schemas">ec_shop</property>
    <property name="usingDecrypt">1</property>
</user>
複製代碼

6.2 schema.xml

這裏使用 childTable 來將訂單表和訂單明細表定義爲 ER 表,避免跨分片查詢。並將地址表 area_info 使用 type="global" 聲明爲全局,一樣也是爲了不跨分片查詢:

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

    <schema name="ec_shop" checkSQLschema="false" sqlMaxLimit="100">
        <table name="customers" primaryKey="customer_id" dataNode="dn01"/>
        <table name="products" primaryKey="product_id" dataNode="dn02"/>
        <table name="orders" primaryKey="order_id" dataNode="dn03,dn04" rule="mod-long">
            <childTable name="order_detail" primaryKey="order_detail_id" joinKey="order_id" parentKey="order_id"/>
        </table>
        <table name="area_info" primaryKey="area_id" type="global" dataNode="dn01,dn02,dn03,dn04"/>
    </schema>


    <dataNode name="dn01" dataHost="host01" database="ec_shop_customer"/>
    <dataNode name="dn02" dataHost="host02" database="ec_shop_product"/>
    <dataNode name="dn03" dataHost="host03" database="ec_shop_order"/>
    <dataNode name="dn04" dataHost="host04" database="ec_shop_order"/>

    <dataHost name="host01" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="native" switchType="1">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="192.168.0.226:3306" user="root" password="123456"/>
    </dataHost>

    <dataHost name="host02" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="native" switchType="1">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="192.168.0.227:3306" user="root" password="123456"/>
    </dataHost>

    <dataHost name="host03" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="native" switchType="1">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="192.168.0.228:3306" user="root" password="123456"/>
    </dataHost>

    <dataHost name="host04" maxCon="1000" minCon="10" balance="0" dbType="mysql" dbDriver="native" switchType="1">
        <heartbeat>select user()</heartbeat>
        <writeHost host="master" url="192.168.0.229:3306" user="root" password="123456"/>
    </dataHost>

</mycat:schema>
複製代碼

6.3 rule.xml

定義訂單表所使用的分片規則,這裏使用取模算法做爲示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
    <tableRule name="mod-long">
        <rule>
            <columns>order_id</columns>
            <algorithm>mod-long</algorithm>
        </rule>
    </tableRule>
    <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
        <!-- nodes 節點的數量-->
        <property name="count">2</property>
    </function>
</mycat:rule>
複製代碼

7、Mycat 與 MySQL 8.0

這裏我後端使用的數據庫是 MySQL 8.0.17 ,相比於使用 MySQL 5.6 或 5.7 來整合 Mycat ,多了一些注意事項,主要以下:

7.1 密碼錯誤

即使你在 server.xml 中正確的配置了用戶名和密碼,但在使用 mysql shell 鏈接 Mycat 時,仍是會拋出密碼錯誤的異常:

Access denied for user 'xxx', because password is error
複製代碼

這是因爲從 MySQL 8.0.4 開始使用 caching_sha2_password 做爲認證的插件,而以前版本的插件爲 mysql_native_password,我在測試中使用的 Mycat 版本爲 1.6.7,它並不支持 caching_sha2_password 。所以在登陸時候須要經過 --default_auth 來指定使用原有的認證插件:

# 1.6.7 版本 Mycat 默認的鏈接端口號爲 8066
mysql -uheibaiying -p -h127.0.0.1 -P8066 --default_auth=mysql_native_password
複製代碼

7.2 數據庫鏈接失敗

Mycat 和 MySQL 都正常啓動,可是在 Mycat 上執行 SQL 語句失敗,提示無效的數據庫。此時能夠查看 Mycat logs 目錄下的 mycat.log 文件,一般會出現下面所示的異常:

(io.mycat.backend.mysql.nio.MySQLConnectionAuthenticator.handle(MySQLConnectionAuthenticator.java:91)
- can't connect to mysql server ,errmsg:Client does not support authentication protocol requested by
server; consider upgrading MySQL client MySQLConnection
複製代碼

這和上面是一樣的緣由,都是由於認證插件而致使的問題。此時須要修改帳戶所使用的認證插件:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'xxxx';
FLUSH PRIVILEGES;
複製代碼

修改後可使用以下命令進行查看:

mysql> SELECT Host,User,plugin FROM mysql.user;
+---------------+------------------+-----------------------+
| Host          | User             | plugin                |
+---------------+------------------+-----------------------+
| %             | root             | mysql_native_password |
| 192.168.200.% | repl             | mysql_native_password |
| localhost     | mysql.infoschema | caching_sha2_password |
| localhost     | mysql.session    | caching_sha2_password |
| localhost     | mysql.sys        | caching_sha2_password |
| localhost     | root             | mysql_native_password |
+---------------+------------------+-----------------------+
複製代碼

以後再重啓 Mycat 服務就能夠正常鏈接。

參考資料

Mycat 官方指南:www.mycat.io/document/my…

更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…

相關文章
相關標籤/搜索