组复制背景
全同步复制
当主节点提交事务时,必须等待所有的从节点收到 binlog 日志并执行后,主节点才会提交事务。
性能非常差,基本不会使用
异步复制
MySQL 默认的复制模式。有一个主(source)和一个或多个从(replica)。主节点执行事务生成 binlog,然后异步的发送到从节点的relay log, 从服务器重新执行(在基于sql语句的复制)或(基于数据行的复制)。默认情况下所有服务器都拥有数据的完整副本。

假如主发生宕机并且binlog还没来得及被从接收,而切换程序将从提升为新的主,就会出现数据不一致的情况!另外,在高并发的情况下,传统的主从复制,从节点可能会与主产生较大的延迟
半同步复制
需要借助插件实现,半同步复制在异步复制中增加了一个同步步骤。这意味着主节点在提交时等待至少一个(可以配置个数)从节点确认它已收到事务。只有这样,主节点才会恢复提交操作。

当主等待从同步成功的过程中主挂了,这个主事务提交就失败了,客户端也收到了事务执行失败的结果了,但是从上已经将binLog的内容写到Relay Log里了,这个时候,从数据就会多了,但是多了数据一般问题不算严重,多了总比少了好。
组复制
组复制由多个节点组成,当其中一个节点提交事务,组内多数节点可用时,就允许执行事务。保证服务器的高可用,底层使用 Paxos 算法。

使用组复制的 mysql 版本必须大于等于 5.7.17, 一个组最多 9 个节点
环境准备
准备三台已安装mysql5.7,且可以互相访问的服务器,安装mysql5.7参考mysql 安装。
| 服务器ip | 系统 | 已安装软件 |
|---|---|---|
| 192.168.31.200 | centos7.9 | mysql5.7.38 |
| 192.168.31.201 | centos7.9 | mysql5.7.38 |
| 192.168.31.202 | centos7.9 | mysql5.7.38 |
安装
修改 hostname
分别修改三台服务器的 hostname 为 s1、s2、s3
# 192.168.31.200
[root@localhost ~]# hostnamectl set-hostname s1
# 192.168.31.201
[root@localhost ~]# hostnamectl set-hostname s2
# 192.168.31.202
[root@localhost ~]# hostnamectl set-hostname s3
在三台服务器的 /etc/hosts 文件,添加 hostname 映射
192.168.31.200 s1
192.168.31.201 s2
192.168.31.202 s3
开启端口
# 开启 33061 端口用来 MGR 通信
[root@s1 ~]# firewall-cmd --zone=public --add-port=33061/tcp --permanent
# 刷新防火墙
[root@s1 ~]# firewall-cmd --reload
关闭 selinux
关闭 selinux, 否则 MGR 会启动失败。
先临时关闭 setenforce 0,重启服务器会失效
然后永久关闭,修改 /etc/selinux/config ,将SELINUX=enforcing改为SELINUX=disabled

调整 mysql 配置文件
在 /etc/my.cnf 文件 [mysqld] 下面追加,
配置文件如下
# -----------------
# 禁用非 innodb 引擎
# -----------------
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
# -----------------
# 复制设置
# -----------------
# * 服务器编号,s1=1, s2=2, s3=s3 *
server_id=1
# 全局事务标识符开启
gtid_mode=ON
enforce_gtid_consistency=ON
# 同步的数据写入数据库
master_info_repository=TABLE
relay_log_info_repository=TABLE
# 开启组复制必须设置为 NONE
binlog_checksum=NONE
# 开启 binlog
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
# -----------------
# 组复制设置
# -----------------
# 添加组复制插件
plugin_load_add='group_replication.so'
transaction_write_set_extraction=XXHASH64
# 组名,同一个组必须相同,使用 UUID 格式,可以在 mysql 控制台使用 SELECT UUID(); 命令生成
group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
# 服务启动时是否自动开启组复制,推荐手动开启
group_replication_start_on_boot=off
# * 与组成员通信的网络地址(注意端口不是3306,是需要新开的端口) s1=s1:33061, s2=s2:33061, s3=s3:33061*
group_replication_local_address= "s1:33061"
# 组成员
group_replication_group_seeds= "s1:33061,s2:33061,s3:33061"
# 是否引导组,设置为 off
group_replication_bootstrap_group=off
注释中开头结尾带 * 号的话,说明此行代码需要根据环境进行调整
重启服务
[root@s1 ~]# service mysqld restart
创建同步账号
所有节点创建同步账号
# 临时关闭 binlog,防止污染
mysql> SET SQL_LOG_BIN=0;
# 创建rpl_user账户,此账户用于实现主从数据同步
mysql> CREATE USER rpl_user@'%' IDENTIFIED BY 'As123456!';
# 赋予主从同步权限
mysql> GRANT REPLICATION SLAVE ON *.* TO rpl_user@'%';
# 让刚才的修改生效
mysql> FLUSH PRIVILEGES;
# 开启 binlog
mysql> SET SQL_LOG_BIN=1;
如果忘记关闭 binlog 记录,导致 binlog 日志污染的话,加入组复制会失败。可以使用 reset master 命令重置 binlog 日志
加入组复制通道
所有节点使用刚刚创建的同步账号,加入组复制通道
mysql> CHANGE MASTER TO MASTER_USER='rpl_user', MASTER_PASSWORD='As123456!' FOR CHANNEL 'group_replication_recovery';
启动组复制
启动组
整个组需要由单个节点来首次开启组复制,我们在配置文件中设置为关闭了自动引导组group_replication_bootstrap_group=off,所以这里我这里我们使用s1来手动开启:
# 开启引导组
mysql> SET GLOBAL group_replication_bootstrap_group=ON;
# 启动组复制
mysql> START GROUP_REPLICATION;
# 关闭引导组
mysql> SET GLOBAL group_replication_bootstrap_group=OFF;
启动完成后查看组成员,可以看到s1已经成功开启组复制了。
mysql> SELECT * FROM performance_schema.replication_group_members;

节点加入
引导组完成后,从节点只需要启动即可
mysql> START GROUP_REPLICATION;
s2、s3 加入节点后,检查是否加入成功
mysql> SELECT * FROM performance_schema.replication_group_members;

节点退出
mysql> STOP GROUP_REPLICATION;
验证
查看集群情况
mysql> SELECT * FROM performance_schema.replication_group_members;
查看当前节点状态
mysql> SELECT * FROM performance_schema.replication_group_member_stats;
查看主节点
如下图可以看出s1是主库,s2、s3 是从库。
mysql> SHOW STATUS LIKE 'group_replication_primary_member';

模拟主从切换
s1 退出并查看情况
# s1 退出节点
mysql> STOP GROUP_REPLICATION;
# s1 查看组员
mysql> SELECT * FROM performance_schema.replication_group_members;

可以看到 s1 是 OFFLINE 状态,并且看不到其他节点
s2 或 s3 查看节点情况
# 查看组员
mysql> SELECT * FROM performance_schema.replication_group_members;
# 查看主节点
mysql> SHOW STATUS LIKE 'group_replication_primary_member';

可以看到 s2 变成了主节点
s1 重新加入组,并查看情况
# 重新加入组
mysql> START GROUP_REPLICATION;
# 查看组员
mysql> SELECT * FROM performance_schema.replication_group_members;
# 查看主节点
mysql> SHOW STATUS LIKE 'group_replication_primary_member';

可以看到主节点仍然是 s2, 并不会切换成 s1
服务运行后加入新节点
当加入新节点时,原来的组可能已经运行了很久,binlog 日志会很大,或者被删除过,这样会导致数据恢复时间过长或者加入节点失败。所以需要将组里的数据直接备份到新的节点上(需要保留 GTID ),这样只需同步后面新生成的 binlog 就行了。
服务运行后加入新节点备份组数据库
在新节点通过 mysqldump 远程备份 MGR 的整个库,最好使用从节点,防止浪费主节点带宽
[root@localhost ~]# mysqldump --all-databases --single-transaction --triggers --routines --events --host=192.168.31.200 --port=3306 --user=remote --password=As123456! > dump.sql
一定要加上 --single-transaction , 否则会锁表,导致生产环境不可用
加入新增节点
先注释 my.cnf 里的 disabled_storage_engines,因为 mysql 系统表必须使用 MyISAM 引擎(5.7是这样,其他版本不确定)
# disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
在新节点上运行备份数据
[root@localhost ~]# mysql -u remote -pAs123456! < dump.sql
再开启 my.cnf 里的 disabled_storage_engines
disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,MEMORY"
加入组复制通道
mysql> CHANGE MASTER TO MASTER_USER='rpl_user', MASTER_PASSWORD='As123456!' FOR CHANNEL 'group_replication_recovery';
启动组
START GROUP_REPLICATION;
在其他组成员的 my.cnf 的 group_replication_group_seeds 里加上当前新节点,以确保下次重启时能够识别到此新加入的节点。
碰到的问题
- 服务运行后加入新节点步骤中
mysqldump备份的数据会不全,后面删除数据时会由于找不到对应的数据删除,导致MGR中的此节点报错。
原因:mysqldump5.7.35以上的版本存在BUG,设置的 GTID 是在备份结束后的 GTID ,不是旧版本中开始备份时的 GTID,导致备份期间创建的数据丢失。
解决方案:目前 mysql5.7 的最新版本是 5.7.40,官方仍然没有修复此BUG,可以先使用旧版本的 mysqldump 来解决此问题。例如 mysqldump5.7.34。
结语
MGR 发生故障时,虽然可以实现自动主从切换,但是无法提供固定的访问入口,客户端连接时仍需要通过第三方工具来实现。这里推荐使用 proxysql,参考mysql 高可用
参考地址
官方文档:https://dev.mysql.com/doc/refman/5.7/en/group-replication.html
MGR搭建过程中遇到的错误以及解决办法: https://cloud.tencent.com/developer/article/1533657
半同步复制的问题: https://zhuanlan.zhihu.com/p/367194130
添加第4个节点到MGR中:https://dba.stackexchange.com/questions/214416/adding-4th-node-in-mysql-group-replication-stuck-at-recovering
mysqldump 数据丢失问题:https://bugs.mysql.com/bug.php?id=105761