天天看点

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;"