天天看點

MyCAT+MySQL 讀寫分離部署

單一的 MySQL 伺服器一旦當機,會造成單點故障,整個系統無法運轉,是以要有主庫(Master)和從庫(Slave備庫)兩份,主庫故障時自動切換至備庫。另外,大多企業應用是寫的少(INSERT/DELETE/UPDATE)查的多(SELECT),是以還要實作讀寫分離,以便根據讀寫比例分别擴充讀、寫庫節點的數目。這麼複雜的切庫與資料分流,如果在應用内部實作,會很繁瑣,而且耦合度高,可采用資料庫中間件實作這些功能。本文是使用 MyCAT 實作雙主(寫)雙從(讀) MySQL 的部署。

作者:王克鋒

出處:https://kefeng.wang/2016/12/22/mycat-mysql/

版權:自由轉載-非商用-非衍生-保持署名,轉載請标明作者和出處。

1 MySQL 開源中間件比較

1.1 TDDL(alibaba)

不是獨立運作的中間件,以 JAR 包形式供給應用調用;社群已停滞。

1.2 Amoeba(alibaba,陳思儒)

獨立運作的中間件,介于應用和MySQL叢集之間,後端使用JDBC Driver,是以更通用;社群已停滞。

1.3 Cobar(alibaba)

是 Amoeba 的進化版本,後端JDBC Driver改為原生的 MySQL 協定,不通用,但更強大(如主備切換);社群已停滞(2013年)。

1.4 MyCAT

是 Cobar 的進化版本,後端由 BIO 改為 NIO;社群活躍。

号稱是國内最活躍的、性能最好的開源資料庫中間件!

使用者包括中國電信、中國聯通。

2 MyCAT 技術要點

官網: http://mycat.io/

電子書: http://mycat.io/document/Mycat_V1.6.0.pdf

紙質書: http://blog.csdn.net/wind520/article/details/53213691

MyCAT 采用 Java 開發,實作 MySQL 公開的二進制協定,将自己僞裝成 MySQL Server;

應用隻需連接配接 MyCAT 執行 SQL,MyCAT 按照配置的政策,分發 SQL 至相應的 MySQL 節點。

2.1 MyCAT 關鍵特性

  • 支援SQL92标準
  • 支援MySQL、Oracle、DB2、SQL Server、PostgreSQL等DB的常見SQL文法
  • 遵守Mysql原生協定,跨語言,跨平台,跨資料庫的通用中間件代理。
  • 基于心跳的自動故障切換,支援讀寫分離,支援MySQL主從,以及galera cluster叢集。
  • 支援資料的多片自動路由與聚合,支援sum,count,max等常用的聚合函數,支援跨庫分頁。
  • 支援單庫内部任意join,支援跨庫2表join,甚至基于caltlet的多表join。
  • 支援通過全局表,ER關系的分片政策,實作了高效的多表join查詢。
  • 支援全局序列号,解決分布式下的主鍵生成問題。
  • 分片規則豐富,插件化開發,易于擴充。
  • 強大的web,指令行監控。
  • 支援zookeeper協調主從切換、zk序列、配置zk化(1.6)
  • 支援庫内分表(1.6)
  • 叢集基于ZooKeeper管理,線上更新,擴容,智能優化,大資料處理(2.0開發版)。
  • ……

2.2 資料分片(Sharding)方式分類

資料庫概念的分級:Node(host:ip), Schema(database), Table, Record

資料的分片有兩種模式(想象各 Schema/Table 從左至右排列,同表 Record 從上至下排列):

  • 垂直分片:不同 Schema/Table 拆分到不同的 Node,根據 Schema/Table 名稱指定 Node;
  • 水準分片:同表的 Record 拆分到多台 Node 中,是根據列值公式結果分散到 Node。

2.3 分片帶來的問題

  • 分布式事務;
  • 跨節點 JOIN;
  • 跨節點合并、排序、分頁;
  • 多資料源管理。

2.4 典型的分片規則

  • 根據 UserID 取餘: 相同使用者的資料處于同一個庫;
  • 根據日期,比如同月或同日的處于同一個庫;
  • 其他字段求餘,或者根據取值範圍,處于同一個庫。

3 MySQL 準備 4 個資料庫

關于 MySQL 單機多執行個體,請參照我前面的文章:MySQL 安裝及其單機多執行個體部署

将建立端口号為 33061/33062/33063/33064 的 4 個 MySQL 執行個體。

3.1 停用之前的兩個資料庫

停用 [mysqld], 端口 3306 将讓給 MyCAT 使用(僞裝成原 [mysqld])

删除 [[email protected]],将改建為雙主雙從 4 個資料庫

sudo systemctl stop mysqld
sudo systemctl disable mysqld
## 暫保留 MySQL 安裝時的 [mysqld] 及其資料檔案
## 暫保留防火牆中的 3306 端口,讓給 MyCAT 使用

sudo systemctl stop [email protected]
sudo systemctl disable [email protected]
sudo rm -rf /var/lib/mysql-slave/ /var/log/mysqld-slave.log ## 删除資料和日志檔案
sudo vim /etc/my.cnf ## 删除 [[email protected]] 一節

sudo vim /etc/sysconfig/iptables ## 删除 --dport 33061 一行
sudo systemctl restart iptables
           

3.2 增加 4 個 [[email protected]] 配置

## sudo vim /etc/my.cnf
[mysqld@master1]
port=
datadir=/var/lib/mysql-master1
socket=/var/lib/mysql-master1/mysql.sock
log-error=/var/log/mysqld-master1.log
explicit_defaults_for_timestamp=true
log_timestamps=SYSTEM

[mysqld@master2]
port=
datadir=/var/lib/mysql-master2
socket=/var/lib/mysql-master2/mysql.sock
log-error=/var/log/mysqld-master2.log
explicit_defaults_for_timestamp=true
log_timestamps=SYSTEM

[mysqld@slave1]
port=
datadir=/var/lib/mysql-slave1
socket=/var/lib/mysql-slave1/mysql.sock
log-error=/var/log/mysqld-slave1.log
explicit_defaults_for_timestamp=true
log_timestamps=SYSTEM

[mysqld@slave2]
port=
datadir=/var/lib/mysql-slave2
socket=/var/lib/mysql-slave2/mysql.sock
log-error=/var/log/mysqld-slave2.log
explicit_defaults_for_timestamp=true
log_timestamps=SYSTEM
           

3.3 啟動 4 個服務

如果建立過一次,現在要重建,則要先删除:

sudo systemctl stop [email protected]{master1,master2,slave1,slave2}
sudo systemctl disable [email protected]{master1,master2,slave1,slave2}
sudo rm -rf /var/lib/mysql-{master1,master2,slave1,slave2}
sudo rm -rf /var/log/mysqld-{master1,master2,slave1,slave2}.log
           

設定為開機自動啟動,并立即啟動:

sudo systemctl enable [email protected]{master1,master2,slave1,slave2}
sudo systemctl start [email protected]{master1,master2,slave1,slave2}
           

3.4 修改 root 密碼

sudo grep "temporary password" /var/log/mysqld-{master1,master2,slave1,slave2}.log # 查詢初始密碼
mysql -uroot -P33061 -p ## 分别執行 33061,33062,33063,33064
MySQL> SET PASSWORD = 'MySQL5.7'; EXIT;
           

4 MySQL 開啟庫間複制

雙主(M1/M2)雙從(S1/S2),開啟主從複制:M1 => M2/S1/S2; M2 => M1/S1/S2

需要注意的是,多主單從複制,是 MySQL 5.7(2013-04-23)才有的特性(FOR CHANNEL),是以 MySQL 版本必須是 5.7+。

https://www.percona.com/blog/2013/10/02/mysql-5-7-multi-source-replication/

http://www.oschina.net/translate/mysql-5-7-multi-source-replication

4.1 MySQL 各執行個體增加複制相關配置

## sudo vim /etc/my.cnf
## 其中的 master_info_repository/relay_log_info_repository 用于支援多主單從複制
[[email protected]]
server_id=33061
log_bin=mysql-bin
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-ignore-db=mysql
binlog-ignore-db=sys

[[email protected]]
server_id=33062
log_bin=mysql-bin
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-ignore-db=mysql
binlog-ignore-db=sys

[[email protected]]
server_id=33063
log_bin=mysql-bin
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-ignore-db=mysql
binlog-ignore-db=sys

[[email protected]]
server_id=33064
log_bin=mysql-bin
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-ignore-db=mysql
binlog-ignore-db=sys
           

儲存後重新開機所有 MySQL 服務:

sudo systemctl restart [email protected]{master1,master2,slave1,slave2}
           

4.2 MySQL 開啟複制

4.2.1 設定 M1 => M2/S1/S2

## 注:為示範友善,此處密碼放在指令行,安全級别高的環境應避免此方式。
mysql -uroot -pMySQL5. -P33061 -e "GRANT REPLICATION SLAVE ON *.* to 'master1'@'localhost' IDENTIFIED BY 'Master5.7';"
mysql -uroot -pMySQL5. -P33062 -e "CHANGE MASTER TO master_host='localhost', master_port=33061, master_user='master1', master_password='Master5.7' FOR CHANNEL 'M1';"
mysql -uroot -pMySQL5. -P33063 -e "CHANGE MASTER TO master_host='localhost', master_port=33061, master_user='master1', master_password='Master5.7' FOR CHANNEL 'M1';"
mysql -uroot -pMySQL5. -P33064 -e "CHANGE MASTER TO master_host='localhost', master_port=33061, master_user='master1', master_password='Master5.7' FOR CHANNEL 'M1';"
           

4.2.2 設定 M2 => M1/S1/S2

mysql -uroot -pMySQL5 -P33062 -e "GRANT REPLICATION SLAVE ON *.* to 'master2'@'localhost' IDENTIFIED BY 'Master5.7';"
mysql -uroot -pMySQL5 -P33061 -e "CHANGE MASTER TO master_host='localhost', master_port=33062, master_user='master2', master_password='Master5.7' FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33063 -e "CHANGE MASTER TO master_host='localhost', master_port=33062, master_user='master2', master_password='Master5.7' FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33064 -e "CHANGE MASTER TO master_host='localhost', master_port=33062, master_user='master2', master_password='Master5.7' FOR CHANNEL 'M2';"
           

4.2.3 立即啟動複制

相關資料: http://dev.mysql.com/doc/refman/5.7/en/start-slave.html

mysql -uroot -pMySQL5 -P33061 -e "START SLAVE FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33062 -e "START SLAVE FOR CHANNEL 'M1';"
mysql -uroot -pMySQL5 -P33063 -e "START SLAVE FOR CHANNEL 'M1'; START SLAVE FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33064 -e "START SLAVE FOR CHANNEL 'M1'; START SLAVE FOR CHANNEL 'M2';"

mysql -uroot -pMySQL5 -P33061 -e "SHOW SLAVE STATUS\G" | grep -E "Master_Host|Master_User|Master_Port|Last_Error"
mysql -uroot -pMySQL5 -P33062 -e "SHOW SLAVE STATUS\G" | grep -E "Master_Host|Master_User|Master_Port|Last_Error"
mysql -uroot -pMySQL5 -P33063 -e "SHOW SLAVE STATUS\G" | grep -E "Master_Host|Master_User|Master_Port|Last_Error"
mysql -uroot -pMySQL5 -P33064 -e "SHOW SLAVE STATUS\G" | grep -E "Master_Host|Master_User|Master_Port|Last_Error"

mysql -uroot -pMySQL5 -P33061 -e "STOP SLAVE FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33062 -e "STOP SLAVE FOR CHANNEL 'M1';"
mysql -uroot -pMySQL5 -P33063 -e "STOP SLAVE FOR CHANNEL 'M1'; STOP SLAVE FOR CHANNEL 'M2';"
mysql -uroot -pMySQL5 -P33064 -e "STOP SLAVE FOR CHANNEL 'M1'; STOP SLAVE FOR CHANNEL 'M2';"
           

4.2.4 建立測試資料表

在 M1 執行,會自動複制至 M2/S1/S2。

mysql -uroot -pMySQL5 -P33061 -e "CREATE DATABASE mydb; CREATE TABLE mydb.mytab(id INT AUTO_INCREMENT, port INT, PRIMARY KEY(id));"
mysql -uroot -pMySQL5 -P33061 -e "DESC mydb.mytab;"
mysql -uroot -pMySQL5 -P33062 -e "DESC mydb.mytab;"
mysql -uroot -pMySQL5 -P33063 -e "DESC mydb.mytab;"
mysql -uroot -pMySQL5 -P33064 -e "DESC mydb.mytab;"
           

5 MyCAT 部署

5.1 安裝

https://github.com/MyCATApache/Mycat-download

https://github.com/MyCATApache/Mycat-download/tree/master/1.5-RELEASE

## 11M, 如果 wget 下載下傳慢,可在 Windows 下載下傳
wget https://github.com/MyCATApache/Mycat-download/raw/master/-RELEASE/Mycat-server--RELEASE--linux.tar.gz
sudo tar -zxf Mycat-server--RELEASE--linux.tar.gz -C /opt
export MYCAT_HOME=/opt/mycat
           

5.2 配置 JVM 參數

## sudo vim $MYCAT_HOME/conf/wrapper.conf
wrapper.java.additional=-XX:MaxDirectMemorySize=M
wrapper.java.additional=-Xmx128M
wrapper.java.additional=-Xms128M
           

5.3 配置讀寫分離規則

雙主雙從讀寫分離: balance=”1” writeType=”0” switchType=”1”

(1)dataHost.balance(READ 目标庫)

balance=”0”, READ => 目前 writeHost, 不開啟讀寫分離機制;

balance=”1”, READ => readHost|閑置的 writeHost

在雙主雙從模式下(M1 <–> M2, M1 -> S1, M2 -> S2),M1 正常時,M2/S1/S2 都參與 SELECT 的負載均衡;

balance=”2”, READ => readhost|所有的 writeHost;

balance=”3”, READ => readhost;

(2)dataHost.writeType(WRITE 目标庫)

writeType=”0”, WRITE => writeHost

writeType=”1”, WRITE => readHost

writeType=”2”, WRITE => writeHost|readhost

(3)dataHost.switchType(切換的模式)

switchType=”-1”, 不自動切換

switchType= “1”, 自動切換(預設值)

switchType= “2”, 基于MySQL主從同步的狀态決定是否切換,心跳語句為 show slave status

switchType= “3”, 基于MySQL galary cluster的切換機制(适合叢集)(1.4.1),心跳語句為 show status like ‘wsrep%’。

## sudo vim $MYCAT_HOME/conf/schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://org.opencloudb/" >

    <schema name="mydb" checkSQLschema="false" sqlMaxLimit="100" dataNode="myNode" />

    <dataNode name="myNode" dataHost="myHost" database="mydb" />

    <dataHost name="myHost" maxCon="1000" minCon="10" dbType="mysql"
        dbDriver="native" balance="1" writeType="0" switchType="1">
        <heartbeat>SELECT 1</heartbeat>
        <writeHost host="master1" url="localhost:33061" user="root" password="MySQL5.7">
            <readHost host="slave1" url="localhost:33063" user="root" password="MySQL5.7" />
        </writeHost>
        <writeHost host="master2" url="localhost:33062" user="root" password="MySQL5.7">
            <readHost host="slave2" url="localhost:33064" user="root" password="MySQL5.7" />
        </writeHost>
    </dataHost>
</mycat:schema>
           

5.4 配置 MyCAT 代理

  • 指定 MyCAT 端口:3306 為應用端口,3308 為管理端口;
  • 設定用戶端使用者的帳号/密碼:admin 有讀寫權限,guest 隻有讀權限。
## sudo vim $MYCAT_HOME/conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://org.opencloudb/">
    <system>
        <property name="defaultSqlParser">druidparser</property>
        <property name="serverPort">3306</property>
        <property name="managerPort">3308</property>
    </system>

    <user name="admin">
        <property name="password">admin123</property>
        <property name="schemas">mydb</property>
    </user>

    <user name="guest">
        <property name="password"></property>
        <property name="schemas">mydb</property>
        <property name="readOnly">true</property>
    </user>
</mycat:server>
           

5.5 配置為 systemd 服務

## sudo vim /usr/lib/systemd/system/mycat.service
## 注意: ExecStart/ExecStop 取值兩側不要用引号
[Unit]
Description=MyCAT Service
After=syslog.target network.target

[Service]
Type=forking
ExecStart=/opt/mycat/bin/mycat start
ExecStop=/opt/mycat/bin/mycat stop

[Install]
WantedBy=multi-user.target
           

設定開機自動啟動、并立即啟動:

sudo systemctl enable mycat # 設定為開機自動啟動
sudo systemctl start mycat # 立即啟動

# sudo systemctl stop mycat # 立即停止
# sudo systemctl disable mycat # 取消開機自動啟動
           

6 驗證部署

6.1 驗證讀資料

連續 100 次連接配接 MyCAT 查詢,根據 @@port 得知 MyCAT 将指令傳遞哪個 MySQL 結點執行。

6.1.1 MySQL 所有結點都在運作的情形

現象:讀資料時,是均衡地從目前存活的“閑置的 writeHost”(M2)和“readHost”(S1/S2) 中讀取。

6.1.2 MySQL 單個結點 S2 當機的情形

現象:讀資料時,是均衡地從目前存活的“閑置的 writeHost”(M2)和“readHost”(S1) 中讀取。

sudo systemctl stop [email protected]
sudo netstat -natp | grep LISTEN | grep mysqld
for i in {..}; do mysql -sN -uguest -e "SELECT @@port"; done
sudo systemctl start [email protected]
           

6.2 驗證寫資料

6.2.1 MySQL 所有結點都在運作

現象:所有 SQL 都傳遞給 writeHost M1 執行。

mysql -sN -uadmin -padmin123 -Dmydb -e "TRUNCATE TABLE mydb.mytab;"
for i in {}; do mysql -sN -uadmin -padmin123 -e "INSERT INTO mydb.mytab(port) VALUES(@@port);"; done
mysql -sN -uguest -e "SELECT port,COUNT(1) FROM mydb.mytab GROUP BY port ORDER BY port;"
           

6.2.2 MySQL 單個結點 M1 當機的情形

現象:所有 SQL 都傳遞給 writeHost M2 執行。

mysql -sN -uadmin -padmin123 -Dmydb -e "TRUNCATE TABLE mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33061 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33062 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33063 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33064 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"

sudo systemctl stop [email protected]
for i in {}; do mysql -sN -uadmin -padmin123 -e "INSERT INTO mydb.mytab(port) VALUES(@@port);"; done
mysql -sN -uguest -e "SELECT port,COUNT(1) FROM mydb.mytab GROUP BY port ORDER BY port;"
           

6.2.3 MySQL 單個結點 M1 恢複的情形

現象:當機期間的資料變更已經同步至 M1。

sudo systemctl start [email protected]
mysql -sN -uroot -pMySQL5 -P33061 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33062 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33063 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"
mysql -sN -uroot -pMySQL5 -P33064 -e "SELECT @@port, COUNT(*) FROM mydb.mytab;"