一. 背景介绍
基于传统异步复制和半同步复制的缺陷——数据的一致性问题无法保证,MySQL官方在5.7.17版本正式推出组复制(MySQL Group Replication,简称MGR)。
由若干个节点共同组成一个复制组,一个事务的提交,必须经过组内大多数节点(N / 2 + 1)决议并通过,才能得以提交。如上图所示,由3个节点组成一个复制组,Consensus层为一致性协议层,在事务提交过程中,发生组间通讯,由2个节点决议(certify)通过这个事务,事务才能够最终得以提交并响应。
引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问题。组复制依靠分布式一致性协议(Paxos协议的变体),实现了分布式下数据的最终一致性,提供了真正的数据高可用方案(是否真正高可用还有待商榷)。其提供的多写方案,给我们实现多活方案带来了希望。
1.MGR特点
MySQL Group Replication(MGR)是MySQL官方在5.7.17版本引进的一个数据库高可用与高扩展的解决方案,以插件形式提供,实现了分布式下数据的最终一致性,总结MGR特点如下:
高一致性:基于分布式paxos协议实现组复制,保证数据一致性;
高容错性:自动检测机制,只要不是大多数节点都宕机就可以继续工作,内置防脑裂保护机制;
高扩展性:节点的增加与移除会自动更新组成员信息,新节点加入后,自动从其他节点同步增量数据,直到与其他节点数据一致;
高灵活性:提供单主模式和多主模式,单主模式在主库宕机后能够自动选主,所有写入都在主节点进行,多主模式支持多节点写入。
2.组复制故障检测
故障检测是提供关于哪些 server 可能已死的信息(猜测)的分布式服务。 某个 server 无响应时触发猜测,组中其余成员进行协调决定以排除给定成员。如果某个 server 与组的其余成员隔离,则它会怀疑所有其他 server 都失败了。由于无法与组达成协议(因为它无法确保仲裁成员数),其怀疑不会产生后果。当服务器以此方式与组隔离时,它无法执行任何本地事务。 在线 server 列表通常称为视图,新成员server的加入离开,无论是自愿还是被迫的离开,该组都会动态地重新规划其配置,并触发视图更新。
3.组复制的限制
存储引擎必须为Innodb,即仅支持InnoDB表,并且每张表一定要有一个主键,用于做write set的冲突检测;
每个表必须提供主键;
只支持ipv4,网络需求较高;
必须打开GTID特性,二进制日志格式必须设置为ROW,用于选主与write set;
COMMIT可能会导致失败,类似于快照事务隔离级别的失败场景;
目前一个MGR集群组最多支持9个节点;
不支持外键于save point特性,无法做全局间的约束检测与部分部分回滚;
二进制日志binlog不支持Replication event checksums;
多主模式(也就是多写模式) 不支持SERIALIZABLE事务隔离级别;
多主模式不能完全支持级联外键约束;
多主模式不支持在不同节点上对同一个数据库对象并发执行DDL(在不同节点上对同一行并发进行RW事务,后发起的事务会失败);
MGR方案本身并不具备故障转移和负载均衡的能力,只有搭配其他第三方中间件后,才可以实现故障转移及负载均衡。
MGR具有故障恢复的能力,所谓故障恢复指的是主库挂掉后,拥有重新选举主库的能力,并且当老的主库恢复后,可以自动重新加入集群,而不需要人工修复。
由于MGR目前功能并不完整,主要是官方尚未推出故障转移和负载均衡的对应解决方案,导致还需要其他第三方中间件配合实现,比较繁琐且复杂,
所以公司目前主要还是采用MHA作为MySQL高可用方案,相信MGR会是今后MySQL最主流的高可用方案
二.单主模式安装环境
在单主模式下,只有一个节点可以可以读写,其他节点只能提供读,在单主模式下,该参数 group_replication_enforce_update_everywhere_checks 必须被设置为 FALSE ,
当主节点宕掉,自动会根据服务器的server_uuid变量和group_replication_member_weight变量值,选择下一个slave谁作为主节点,group_replication_member_weight的值最高的成员被选为新的主节点,
在group_replication_member_weight值相同的情况下,group根据数据字典中 server_uuid排序,排序在最前的被选择为主节点
db1:192.168.10.128
db2:192.168.10.129
db3:192.168.10.130
修改主机名
192.168.10.128 hostnamectl set-hostname db1
192.168.10.129 hostnamectl set-hostname db2
192.168.10.130 hostnamectl set-hostname db3
在/etc/hosts添加
192.168.10.128 db1
192.168.10.129 db2
192.168.10.130 db3
1. 创建用户
useradd mysql
2. 上传软件并解压
tar -xvf mysql-5.7.33-linux-glibc2.12-x86_64.tar.gz -C /data/
cd /data
mv mysql-5.7.33-linux-glibc2.12-x86_64/ mysql
3. 环境变量
vi /etc/profile
export PATH=$PATH:/data/mysql/bin
mkdir -p /data/3306/data
chown -R mysql.mysql /data
4. 配置文件说明
配置示例:
++++++++++db1++++++
vi /data/3306/my.cnf
[mysqld]
user=mysql
datadir=/data/3306/data
basedir=/data/mysql
port=3306
socket=/data/3306/mysql.sock
server_id=28
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="22d56f7c-dfe5-4eb1-a21a-cf9c27e8d625"
loose-group_replication_ip_whitelist = "127.0.0.1/24,192.168.10.0/24"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address="192.168.10.128:33061"
loose-group_replication_group_seeds="192.168.10.128:33061,192.168.10.129:33061,192.168.10.130:33061"
loose-group_replication_bootstrap_group=off
loose-group_replication_single_primary_mode=true
loose-group_replication_enforce_update_everywhere_checks= false
++++++++++db2++++++
vi /data/3306/my.cnf
[mysqld]
user=mysql
datadir=/data/3306/data
basedir=/data/mysql
port=3306
socket=/data/3306/mysql.sock
server_id=29
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="22d56f7c-dfe5-4eb1-a21a-cf9c27e8d625"
loose-group_replication_ip_whitelist = "127.0.0.1/24,192.168.10.0/24"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address="192.168.10.129:33061"
loose-group_replication_group_seeds="192.168.10.128:33061,192.168.10.129:33061,192.168.10.130:33061"
loose-group_replication_bootstrap_group=off
loose-group_replication_single_primary_mode=true
loose-group_replication_enforce_update_everywhere_checks= false
++++++++++db3++++++
vi /data/3306/my.cnf
[mysqld]
user=mysql
datadir=/data/3306/data
basedir=/data/mysql
port=3306
socket=/data/3306/mysql.sock
server_id=30
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="22d56f7c-dfe5-4eb1-a21a-cf9c27e8d625"
loose-group_replication_ip_whitelist = "127.0.0.1/24,192.168.10.0/24"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address="192.168.10.130:33061"
loose-group_replication_group_seeds="192.168.10.128:33061,192.168.10.129:33061,192.168.10.130:33061"
loose-group_replication_bootstrap_group=off
loose-group_replication_single_primary_mode=true
loose-group_replication_enforce_update_everywhere_checks= false
组复制部分,配置文件介绍:
group_replication变量使用的loose-前缀是指示Server启用时尚未加载复制插件也将继续启动
transaction_write_set_extraction = XXHASH64
##指示Server必须为每个事务收集写集合,并使用XXHASH64哈希算法将其编码为散列
loose-group_replication_group_name="01e5fb97-be64-41f7-bafd-3afc7a6ab555"
##表示将加入或者创建的复制组命名为01e5fb97-be64-41f7-bafd-3afc7a6ab555
##可自定义(通过cat /proc/sys/kernel/random/uuid)
loose-group_replication_start_on_boot=off
##设置为Server启动时不自动启动组复制
loose-group_replication_local_address="192.168.10.128:33061"
##绑定本地的192.168.10.128及33061端口接受其他组成员的连接,IP地址必须为其他组成员可正常访问
loose-group_replication_group_seeds="192.168.10.128:33061,192.168.10.128:33062,192.168.10.128:33063"
##本行为告诉服务器当服务器加入组时,应当连接到192.168.10.128:33061,192.168.10.128:33062,192.168.10.128:33063
##这些种子服务器进行配置。本设置可以不是全部的组成员服务地址。
loose-group_replication_bootstrap_group = off
##配置是否自动引导组
loose-group_replication_ip_whitelist="10.30.0.0/16,10.31.0..0/16,10.27.0.0/16"
##配置白名单,默认情况下只允许192.168.10.128,192.168.10.129,192.168.10.130连接到复制组,如果是其他IP则需要配置。
5. 初始化数据,并启动数据库节点
mysqld --initialize-insecure --user=mysql --basedir=/data/mysql --datadir=/data/3306/data
mysqld_safe --defaults-file=/data/3306/my.cnf &
6. db1:3306节点加入GR
mysql -S /data/3306/mysql.sock
加载GR插件
install plugin group_replication soname 'group_replication.so';
show plugins;
创建复制用户
set sql_log_bin=0;
grant replication slave on *.* to repl@'%' identified by '123';
flush privileges;
SET SQL_LOG_BIN=1;
开启分布式复制
change master to master_user='repl',master_password='123' for channel 'group_replication_recovery';
启动复制程序
set global group_replication_bootstrap_group=ON;
start group_replication;
set global group_replication_bootstrap_group=OFF;
#检测组是否创建并已加入新成员
select * from performance_schema.replication_group_members;
注: 前面的用户密码修改和创建用户操作必须设置binlog不记录,执行后再打开,否则会引起START GROUP_REPLICATION执行报错:
ERROR 3092 (HY000): The server is not configured properly to be an active member of the group. Please see more details on error log.
解决方案是:根据提示打开group_replication_allow_local_disjoint_gtids_join选项,mysql命令行执行:
mysql> set global group_replication_allow_local_disjoint_gtids_join=ON;
然后再执行:
mysql> start group_replication;
7. db2:3306加入GR:
mysql -S /data/3306/mysql.sock
加载GR插件
install plugin group_replication soname 'group_replication.so';
show plugins;
创建复制用户
set sql_log_bin=0;
grant replication slave on *.* to repl@'%' identified by '123';
flush privileges;
SET SQL_LOG_BIN=1;
开启分布式复制
change master to master_user='repl',master_password='123' for channel 'group_replication_recovery';
启动复制程序
start group_replication;
#检测组是否创建并已加入新成员
select * from performance_schema.replication_group_members;
8.db3:3306加入GR
mysql -S /data/3306/mysql.sock
加载GR插件
install plugin group_replication soname 'group_replication.so';
show plugins;
创建复制用户
set sql_log_bin=0;
grant replication slave on *.* to repl@'%' identified by '123';
flush privileges;
SET SQL_LOG_BIN=1;
开启分布式复制
change master to master_user='repl',master_password='123' for channel 'group_replication_recovery';
启动复制程序
start group_replication;
#检测组是否创建并已加入新成员
select * from performance_schema.replication_group_members;
#检测组是否创建并已加入新成员
select * from performance_schema.replication_group_members;
9、插入数据
db1
create database qingchen charset utf8mb4;
use qingchen
create table t1(id int primary key); ##表必须带主键
insert into t1 values (1);
db2和db3都可以查询到
db2和db3插入数据会报错
10、主库宕机模拟
db1属于主库,模拟其主库宕机,db1宕机后,db2及db3检查到 timeout reading,则会从group_member中剔除该实例,同时检测宕机实例是否小于 floor((n-1)/2) (n为group中所有实例个数),
如果满足,则启动新的GROUP,按照GROUP中各个实例的UUID进行 升序排序,选择第一个作为新的主库,由于新主库之前是super_read_only状态,仅支持只读,升级为新主库后,
会执行 ,不设置 super_read_only,关闭此参数,那么新主库则是可提供读写服务,原先的从库现在依旧为从库,super_read_only依旧为启动状态,仅提供读服务。
db1:
mysqladmin -S /data/3306/mysql.sock shutdown
tail -f /data/3306/data/db2.err ###db2节点观察切换过程
在其他节点查询
select * from performance_schema.replication_group_members;
如何判断哪个节点为主,只有主节点可以写入
show variables like '%read_only';
发现主节点切换到了db2
创建新数据
create table t2(id int primary key);
insert into t2 values (1);
flush logs;
insert into t2 values (2);
insert into t1 values (2);
然后将db1节点启动
mysqld_safe --defaults-file=/data/3306/my.cnf &
start group_replication;
select * from performance_schema.replication_group_members;
tail -f /data/3306/data/db1.err ##通过日志文件来查看过程
通过error log大小的变化,是通过group_replication_recovery 通道来恢复数据,需要恢复的binary log是存放在 *_recovery.* 系列文件 ,通过本次recovery 文件查看,发现,在recovery过程中,通道内的IO_THREAD拉去日志存储在 *_recovery.* 系列文件 中,当通道内的 SQL_Thread 完成日志应用后,则会删除掉 *_recovery.* 系列文件 文件,新建空文件,代表已经没有数据需要恢复。
至此,单主模式已搭建结束,实例A可提供读写,但是实例B跟实例C仅提供读服务。
group_replication_applier 通道 提供组内成员向 MASTER 实时同步binlog日志使用,这个通道内IO_thread拉取到的日志存放在 *_apaplier.* 系列文件中,再通过SQL_Thread应用到组内的各个SERVER上。
group_replication_recovery 通道 提供 首次加入GROUP或者重新加入GROUP时恢复数据使用,这个通道内 IO_thread拉取到的日志存放在 *_recovery.* 系列文件中,再通过SQL_Thread应用到组内的各个SERVER上,应用结束后,删除所有 *_recovery.* 系列文件 ,重新建立新的 *_recovery.* 系列文件。
可以通过P_S库中的表格查询使用情况:SELECT * FROM mysql.slave_relay_log_info
11、如果同时宕机两台
db1和db2同时执行
mysqladmin -S /data/3306/mysql.sock shutdown
db3切换为主库,正常可以读写
三、单主切换多主
在mysql多主模式下,在组复制中通过Group Replication Protocol协议及Paxos协议,形成的整体高可用解决方案 同时增加了certify的概念,负责检查事务是否允许提交,是否与其它事务存在冲突,
Group Replication是由多个节点共同组成一个数据库集群,每个节点都可以单独执行事务,但是read-write(rw)的操作只有在组内验证后才可以commit,Read-only (RO)事务是不需要验证可以立即执行,
当一个事务在一个节点上提交之前,会在组内自动进行原子性的广播,告知其他节点变更了什么内容/执行了什么事务,然后为该事物建立一个全局的排序,
最终,这意味着所有的服务器都以相同的顺序接收相同的事务集。因此,所有服务器都按照相同的顺序应用相同的变更集,因此它们在组中保持一致
隔离级别
官网建议使用READ COMMITTED级别,除非应用程序依赖于REPLEATABLE READ,RC模式下没有GAP LOCK,比较好支持Innodb本身的冲突检测机制何组复制的内部分布式检测机制一起协同工作。不支持SERIALIZABLE隔离级别
外键
不建议使用级联外键,如果使用必须配置group_replication_enforce_update_everywhere_checks=ON
DDL操作
多主不支持同一对象不同实例的并发的DDL+DML混合操作 ,MySQL5.7上的DDL不是原子操作无法回滚,因此group replication没有对DDL做冲突检测,可能导致数据不一致;
应用或中间件要能够把所有的DDL语句转到同一台MySQL上去执行。
1、如果直接启动
loose-group_replication_single_primary_mode=false
loose-group_replication_enforce_update_everywhere_checks= true
2、单主模式转换为多主
MGR切换模式需要重新启动组复制,因些需要在所有节点上先关闭组复制,设置 group_replication_single_primary_mode=OFF 等参数,再启动组复制。
复制代码
# 停止组复制(所有节点执行):
stop group_replication;
set global group_replication_single_primary_mode=OFF;
set global group_replication_enforce_update_everywhere_checks=ON;
###也可以写入配置文件
loose-group_replication_single_primary_mode=false
loose-group_replication_enforce_update_everywhere_checks= true
# 随便选择某个节点执行
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
# 其他节点执行
START GROUP_REPLICATION;
# 查看组信息,所有节点的 MEMBER_ROLE 都为 PRIMARY
SELECT * FROM performance_schema.replication_group_members;
show variables like '%read_only';###发现三个节点都是off
如果在同时提交DDL语句,则在每个实例都是可以提交成功,但是同步到各个实例的时候会发生报错,group_replication出现 error错误,所有实例启动super_read_only只读情况,整个group不提供 写操作,需要人为接入修复。所以DDL语句,建议在设计的时候,就专门只有一个实例可以执行DDL语句,人为默认在某一台上执行DDL语句,而不是每台都执行,避免不必要的冲突。
四、多主切换回单主模式
复制代码
# 所有节点执行
stop group_replication;
set global group_replication_enforce_update_everywhere_checks=OFF;
set global group_replication_single_primary_mode=ON;
#主节点(db1)执行
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
# 从节点(db2、db3)执行
START GROUP_REPLICATION;
# 查看MGR组信息
SELECT * FROM performance_schema.replication_group_members;
show variables like 'read_only';
只有db1是off
五、部分补充
1、部分表及功能
2、检查数据库读写
show global variables like 'super%';
和show variables like 'read_only';一样,
on的可读,off可读可写
3、单主新增从库innobackupex
选择在 db1备份实例,备份结束后apply log。
innobackupex --datadir=/data/3306/data/ --user=root --password= --no-timestamp --socket=/data/3306/mysql.sock /data/backup
innobackupex --apply-log /data/backup3320
第一次启动数据库时,报错,找不到relay log文件,因为拷贝过来的时候 ,备份库指定参数如下,mysql库中的master_relay_log_info指定了relay log的相关信息,但是现在没有找到文件,
数据库会自动创建 applier跟recovery系列文件。
master_info_repository=TABLE
relay_log_info_repository=TABLE
所以需要进入数据库中, truncate 两个表格:mysql.slave_master_info, mysql.slave_relay_log_info ,然后删除 applier跟recovery系列文件 。
truncate table mysql.slave_master_info
truncate table mysql.slave_relay_log_info3
rm -rf applier系列文件
m -rf recovery系列文件
查看下备份的GTID集合
cat xtrabackup_binlog_info
重启数据库服务,进入数据库,重新配置GTID集合与备份中的一致,启动GROUP_REPLICATION。
RESET MASTER;
SET @@GLOBAL.GTID_PURGED='9ac06b4e-13aa-11e7-a62e-5254004347f9:1-10';
CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='123' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
4、单主新增从库:mysqldump新增
备份数据库实例:
mysqldump --socket=/data/3306/mysql.sock -uroot -p --all-databases > /data/backup/full.sql
这里有个小TIPS,个人建议,建立一个新的实例后,在新实例中安装 好 group_replication 引擎,不要等到source后再安装,这样的好处是:避免直接在恢复的数据库实例上安装引擎,会出现各种错误。
在服务器上先安装 group_replication引擎,然后再source数据,避免source数据后由于环境问题导致group_replication引擎安装有问题
INSTALL PLUGIN group_replication SONAME 'group_replication.so';
成功后source /data/backup/full.sql
检查当前的gtid使用情况,在备份文件里面有,也可以通过show master status查看;
由于目前的使用情况跟mysqldump中的 gtid_purge不一致,重新拷贝下mysql3320.sql中的 gtid_purged语句,注意,如果当前的gtid_excuted不为空,则需要重置下master相关信息,reset master后执行gtid_purge语句。
SET @@GLOBAL.GTID_PURGED='9ac06b4e-13aa-11e7-a62e-5254004347f9:1-18'; #看GTID集合是否一致
CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='123' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
5、多主新增DB:innobackupex新增
简要步骤
(1).备份后执行apply log
(2).新建实例,添加plugins引擎
(3).替换数据目录
(4).启动数据库
(5).清理relay-log文件,清理slave_master_info跟slave_relay_log_info信息
(6).查看当前的GTID序号是否与 xtrabackup_binlog_info记录一致,如果不一致,执行 set gtid_purged
(7).重启数据库服务
(8).检查group replication配置是否有误
(9).change master
(10).start group_replication
#部分参考SQL
SET @@GLOBAL.GTID_PURGED='9ac06b4e-13aa-11e7-a62e-5254004347f9:1-26:1000004';
CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='123' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
6、多主新增DB:mysqldump新增
简要步骤
(1).新建实例,添加plugins引擎
(2).source 备份文件
(3).清理relay-log文件,清理slave_master_info跟slave_relay_log_info信息
(4).查看当前的GTID序号是否与备份文件前面记录一致,如果不一致,执行 set gtid_purged
(5).检查group replication配置是否有误
(6).change master
(7).start group_replication