MySql8主从复制介绍

MySql8主从复制介绍

xc20200316

本文介绍mysql主从复制的基本流程。
和两种方法:

我们用docker的方式运行mysql。清爽简洁。升级等操作也方便。


获取镜像

docker镜像信息
https://hub.docker.com/_/mysql/

docker配置文档
https://dev.mysql.com/doc/mysql-installation-excerpt/8.0/en/docker-mysql-getting-started.html

拉取最新8.0.19镜像。
sudo docker pull mysql:8.0.19


主从复制方法1:binlog + log位置

docker-compose的配置

mysql_8:
    image: mysql:8.0.19
    ...
    volumes:     # 数据目录映射到我们指定的位置
                - "/airm2m_data/airm2m/database/mysql_8/data:/var/lib/mysql"

    environment: # 环境变量     默认root密码
                MYSQL_ROOT_PASSWORD: xxxxxxx

    command:     # 主从复制会用到server-id,所有节点id必须全局唯一。
                --server-id=1

主从复制需要开启binlog。mysql8默认开启,和数据存在同个目录。不用管了。

docker-compose up -d mysql_8

启动后可看到data目录下生成了一批文件

进入mysql后

show variables;

可以查看各种系统变量和配置

到此master就起来了。再用同样方式启动slave。注意server-id要不同。

给master创建一个slave账号,授予REPLICATION SLAVE权限。

进入slave,指定master的ip和端口。

CHANGE MASTER TO
MASTER_HOST='xxxxxxx',
MASTER_PORT=3307

启动复制,用刚才创建的slave账号。

START SLAVE USER='xxx' PASSWORD='xxx'

这时在master上创建数据就会同步到slave了。

在slave上可以查看slave同步的重要数据

show slave status

binlog+pos的原理简单来说就是master产生binlog,slave复制master的binlog到自己的relay log,slave再重放relay log,插入实际数据。
slave会记录最后读取的数据的log文件和位置(master_log_file和read_master_log_pos),保证不会重复同步数据。

出错情景

情景1:stop slave容器再起,或服务器重启。

slave启动后,默认的master_user会变成test,造成连不上master。先stop slave再用用户名密码start slave即可。
master_user估计哪里可以永久设定。先不细究。

情景2:rm slave容器再起。

删除容器再创建,启动slave时会报错

START SLAVE USER='xxx' PASSWORD='xxx'

Error Code: 1872. Slave failed to initialize relay log info structure from the repository

解法:
在slave上

show slave status

记下master_log_file和read_master_log_pos

reset slave;   // 清空了master_log_file。read_master_log_pos设为4

CHANGE MASTER TO
MASTER_HOST='xxx',
MASTER_PORT=3307,
master_log_file='binlog.000005',  // 填记下的master_log_file和read_master_log_pos
master_log_pos=11957

START SLAVE USER='xxx' PASSWORD='xxx' // 启动。完成。

如果read_master_log_pos与rm之前的值不一样,会造成数据不一致。
(填小,会缺数据。而且无论怎么折腾,重设参数,都回复不过来了。目前只好转到情况4,完全重做slave)

情景3:master容器stop再起。rm master容器再起。

不用处理。slave断开后一分钟左右会自动重连。

情景4:slave数据已错乱,无法同步。完全重做slave。需清空slave,dump出master数据,导入slave,再开始同步。

参考:
https://blog.51cto.com/liaoxz/2141797
https://tecadmin.net/reset-re-sync-mysql-master-slave-replication/

目前看到的最好的办法:
用mysqldump,加–master-data=1参数
可不停机dump出现有数据,并附带master_log_file和master_log_pos信息。
总而言之就是dump出一份通用的数据以建立一个新的slave

老版本客户端连8.0会报错,因为密码策略更新了。先升级客户端。(网上解法都是修改服务器默认密码策略,很难受。)
https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/

ubuntu14.04会出错,16.04可以。
先sudo apt-cache search mysql-client。尝试安装的话,都是5的版本。

wget https://dev.mysql.com/get/mysql-apt-config_0.8.15-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.15-1_all.deb

server选8.0

sudo apt-get update
sudo apt-cache search mysql-client
sudo apt install mysql-client          //看到显示是的8.0.

装好后看mysql –version。变成8了

dump出master的数据

mysqldump -uroot -pxxxxx --host xxx --port xxx --single-transaction --master-data --databases test > master_test.sql

打开test.sql
看到CHANGE MASTER TO MASTER_LOG_FILE='binlog.000008', MASTER_LOG_POS=2841;
这里就包含了master的信息

stop slave
reset slave

把slave的数据库都删掉

进入slave。

mysql -uroot -pxxxx --host 127.0.0.1 --port xxxx

执行dump出的sql。

source master_all.sql

START SLAVE USER='xxx' PASSWORD='xxx'

到此数据已经同步


主从复制方法2:GTID

https://dev.mysql.com/doc/refman/8.0/en/replication-gtids.html

GTID的格式

各系统变量和内部表的说明:

https://dev.mysql.com/doc/refman/8.0/en/replication-options-gtids.html

mysql.gtid_executed
GTID会存在mysql内部mysql.gtid_executed表中。

binlog
开启gtid-mode后。GTID也会存在于binlog里。

gtid_next系统变量
控制如何得到下一个GTID,默认值AUTOMATIC,事务执行后自动生成。
还可以手动设为某个特定的GTID,这是非常规操作,不做深究。

gtid_executed系统变量(注意和mysql.gtid_executed表并不同)
包含已执行的gtid
mysql启动时会进行初始化。初始化的依据:现有的binlogmysql.gtid_executed

gtid_purged系统变量
包含已执行但不存在于任何binlog的gtid,是gtid_executed的子集。
为什么不存在于任何binlog?因为binlog通常会被删除(如purge binary logs删除binlog或mysql自动清理binlog(expire_logs_days参数,默认为0不自动删除))
GTID的存在有这么个公式:gtid_executed = binlog + gtid_purged
mysql启动时会进行初始化。初始化的依据:现有的binloggtid_executed系统变量

存在于gtid_purged中的三种GTID:

  1. 已执行的GTID,但slave的binlog没打开。
  2. 写入binlog后,binlog被删除。
  3. 手动设置。SET @@GLOBAL.gtid_purged=’xxxxxxxxxxxxx
思考:

这样看来GTID的实际记录组成就是mysql.gtid_executed表加现有的binlog。其他几个变量都是会开机重新计算并实时变化,系统同步时会频繁用到,也可用于一些手动操作和状态查询。

GTID的生命周期

  1. 一个事务在master上被执行。那么给这个事物分配一个GTID(master的id+transaction_id)并存入binlog。GTID一定是连续的(某些特殊情况出现缺口时,会自动进行填充),并存入binlog的。如果某条没存binlog,那么就不会分配给它GTID。
  2. GTID在分配给一个事务后,在commit时作为一个Gtid_log_event存到binlog。在binlog进行滚动或mysql关闭等情况下会最终写到mysql.gtid_executed
  3. GTID在分配给一个事务后,会存入@@GLOBAL.gtid_executed系统变量里。这个变量是一个GTID集合,用来表示server当前的一个状态。gtid_executed系统变量包含所有已执行的GTID,而mysql.gtid_executed表并不一定包含全部,因为如第2点所说会先存入binlog,在后续的某个时机才会存入mysql.gtid_executed表。
    (我的理解,简单来说就是同时存入@@GLOBAL.gtid_executed系统变量和binlog(可看作是实时的,总是最新的),后续某个时机(binlog滚动等)再存入mysql.gtid_executed表(可看作是非实时的,非最新的)。)
  4. binlog数据传到slave并存入slave的relay log后。slave读出GTID并存入系统变量gtid_next ,告诉系统待同步的事务。
  5. slave拿到这个GTID,验证是否自己独占(slave可多线程进行复制),验证这个GTID是否已经用过等等。
  6. 各种验证都没问题,slave执行这个事务。
  7. 如果slave的binlog是打开的,为一个Gtid_log_event存到binlog。在binlog进行滚动或mysql关闭等情况下会最终写到mysql.gtid_executed
  8. 如果slave的binlog是关闭的,直接存入slave的mysql.gtid_executed表。
  9. slave执行这个GTID之后,会存到@@GLOBAL.gtid_executed系统变量。跟master一样,@@GLOBAL.gtid_executed系统变量也包含所有已执行的GTID。

大致就是这几个表和系统变量标识了GTID的各种状态和阶段,根据binlog的开关和滚动,以及其他设置,最终让所有GTID得到比较完美的同步。
看着挺复杂的,文档内容也比较多,需要仔细看文档甚至源码去学习了。

非事务性的操作也可能分配GTID。比如磁盘故障,造成binlog出现一个缺口,那么这个缺口也会分配一个GTID。

一条语句也能差分成多个事务,从而分配多个GTID。

GTID自动寻址

slave发送已同步过的GTID集合给master。master把slave没有的数据发回去。某些拓扑结构会造成slave收到重复的数据,但slave自己也会判断重复并跳过的。

GTID配置方法

打开gtid_modeenforce-gtid-consistency

# docker-compose 配置
mysql_8:
    image: mysql:8.0.19
    command:
            --server-id=1
            --gtid_mode=on
            --enforce-gtid-consistency=on
            --skip-slave-start=off   # for slave 不在启动时自动同步,而是手动启动同步。

启动后show variables,可看到都为ON。

1. 完全重做数据库。

起一个干净的mysql作为master。创建slave账号。
起一个干净的mysql作为slave。slave要加个skip-slave-start=off,不在启动时自动同步,而是手动启动同步。

slave启动后开始同步

change master to
master_host='xxxxx',
master_user='xxx',
MASTER_AUTO_POSITION = 1;

2.出错场景

如果数据不一致了,有几种解决方法。
这里介绍个最简单的,把数据库整个导出。
比如现有好几个数据库,在slave上把其中一个的几个表或一批数据误删了。这时reset slave再重启也无效。
那么:

1.slave停止复制

stop slave

2.从master上dump出不一致的数据库

mysqldump -uroot -pxxxx --host xxxx --port xxxx --single-transaction --master-data=1 --set-gtid-purged=on --databases test > master_test.sql

3.导入slave。因为都是带GTID的,所以不会重复导入。导入后可以看到数据全了。

mysql -uroot -pxxxxx --host 127.0.0.1 --port xxxx
source master_all.sql

4.slave启动复制。回到正常状态。

START SLAVE USER='xxx' PASSWORD='xxx'

上次更新 2021-01-28