主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。
MySQL 复制的优点主要包含以下三个方面:
在MySQL主从复制集群中,主要分为两大部分:文件、线程。
文件:
线程:
MySQL主从复制集群的工作原理主要围绕前面提到的文件和线程。
MySQL主从复制集群的原理:
1)首先从库执行
change master to
将连接主库的配置信息记录到master.info文件中,此时从库上会开启两个线程:I/O线程和SQL线程。2)从库上的I/O线程会从master.info文件中读取主库的连接信息。
3)从库I/O线程获取到主库的信息后,会与主库进行身份认证,然后建立连接。
4)当从库I/O线程成功连接到主库后,主库会立即给从库分配一个binlog_dump_thread线程,用于推送Binlog日志到从库。
5)从库I/O线程会根据master.info中记录的Binlog信息(Binlog日志文件、标识位号)与主库的binlog_dump_thread线程请求最新的Binlog日志。
6)这时主库的binlog_dump_thread线程就会去查询是否产生了新的Binlog日志,如果产生了新的Binlog日志,会截取最新的Binlog日志然后推送给从库的I/O线程。
7)从库的I/O线程接收到主库推送的Binlog日志后,会现将其存放在内存的TCP/IP缓存中,然后告知主库的binlog_dump_thread线程,Binlog日志已收到。
8)此时从库的I/O线程会去更新master.info文件中的Binlog位置点信息,记录最新的Binlog标识号。
9)然后从库的I/O线程会将主库推送的Binlog日志写入到磁盘上的relay-log文件中。
10)最后由从库的SQL线程读取relay-log.ifno文件,获取relay-log最新的位置点,然后根据的位置点去relay-log中执行最新的Binlog日志,执行完成后会再次更新relay-log.info文件中记录的relay-log位置点。
这就是完整的主从复制工作原理。
简单来说MySQL主从复制的原理就是,从库的I/O线程读取连接主库的配置信息,然后去连接主库开始主从同步,当I/O线程连接上主库后,主库会立即给I/O线程分配一个Dump线程,用于推送Binlog日志到从库,此时I/O线程会根据master.info文件中记录的Binlog信息,向主库的Dump线程请求最新的BInlog,Dump线程查询到有最新的Binlog产生,会将最新的Binlog截取,然后推送给从库的I/O线程,I/O线程收到Binlog日志后,将其存放在内存的TCP/IP缓存中,然后更新master.info文件中最新的Binlog信息,紧接着将Binlog日志写入到relay-log中,最后由从库的SQL线程从relay-log.info中读取relay-log的位置号,然后执行relay-log中最新的Binlog日志,执行完成后,再次更新relay-log.info中的relay-log位置号,以便于下次再relay-log中读取最新的Binlog日志。
relay-log日志会通过MySQL中的其他线程定期清理。
从上面说的主从复制原理来看,好像每次都是从库向主库去请求新数据,那么什么时候从库才应该向主库请求呢?请求的频率如何?
其实是这样的:当主库上产生了新的事务,更新到Binlog日之后,会给binlog_dump_thread线程发送一个“信号”,binlog_dump_thread线程会与从库的I/O线程一直建立连接,binlog_dump_thread线程就会通知从库的I/O线程有新数据产生了,这时从库的I/O线程就带着master.info中记录的最新Binlog标识位号,向binlog_dump_thread线程请求最新的Binlog,然后完成数据同步。
MySQL主从复制第一次复制时,是按照上面说到的10步完成的,第二次复制时,只需要等待主库的binlog_dump_thread线程向从库的I/O线程发送信号,然后I/O线程去请求最新的Binlog,最后由SQL线程复制数据即可。
搭建MySQL主从复制集群的前提条件如下:
本次主从复制的架构是一主两从的结构。
IP | 主机名 | 端口 | 角色 | server_id |
---|---|---|---|---|
192.168.20.11 | mysql-1 | 3306 | 主库 | 1 |
192.168.20.12 | mysql-2 | 3306 | 从库 | 2 |
192.168.20.12 | mysql-2 | 3307 | 从库 | 3 |
再搭建MySQL主从复制集群之前,首先搭建出多个MySQL实例,由于服务器有限,因此通过两台机器模拟出一主两从的环境。
主从复制集群中有一个节点开启了gtid,所有的节点都需要开启gtid,否则主从将不能同步。
mysql-1服务器中的3306端口的MySQL实例再前面已经搭建完成了,就是我们一直在使用的数据库实例,里面有数据,更加方便演示主从集群,下面只是提供一下搭建过程的步骤。
注意每个MySQL节点的server_id都要设置成不同的,如果你的一主两从都是单独在不同服务器中部署的,那么直接参考本小结的数据库实例安装即可,不用再看多实例的步骤了。
1.解压MySQL
[root@mysql-1 ~]# tar xf mysql-5.7.36-linux-glibc2.12-x86_64.tar.gz -C /usr/local/
[root@mysql-1 ~]# mv /usr/local/mysql-5.7.36-linux-glibc2.12-x86_64 /usr/local/mysql
2.设置MySQL的环境变量
[root@mysql-1 ~]# vim /etc/profile
export MYSQL_HOME=/usr/local/mysql
export PATH=$MYSQL_HOME/bin:$PATH
export LD_LIBRARY_PATH=:/usr/local/mysql/lib
3.创建mysql用户
[root@mysql-1 ~]# groupadd -r mysql
[root@mysql-1 ~]# useradd -M -r -s /sbin/nologin -g mysql mysql
4.准备数据目录
[root@mysql-1 ~]# mkdir /data/mysql
[root@mysql-1 ~]# chown -R mysql. /data/mysql
5.初始化数据库
[root@mysql-1 ~]# mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql
6.准备mysql配置文件
[root@mysql-1 ~]# vim /etc/my.cnf
[mysqld]
user=mysql
port=3306
server_id=1 #每个MySQL数据库的server_id都设置成不同的
basedir=/usr/local/mysql
datadir=/data/mysql
log_bin=mysql-bin
gtid-mode=on
enforce-gtid-consistency=true
socket=/tmp/mysql.sock
log_error=/data/mysql/mysql_err.log
character-set-server=utf8
[mysql]
socket=/tmp/mysql.sock
7.准备服务管理脚本
[root@mysql-1 ~]# vim /etc/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE = 5000
8.启动数据库
[root@mysql-1 ~]# systemctl daemon-reload
[root@mysql-1 ~]# systemctl start mysqld
9.设置root密码
[root@mysql-1 ~]# mysqladmin -u root -P 3306 password '123456'
10.登陆数据库
[root@mysql-1 ~]# mysql -uroot -p123456
mysql>
由于服务器数量有限,在mysql-2这台服务器中分别搭建两个从库。
1.解压MySQL
[root@mysql-2 ~]# tar xf mysql-5.7.36-linux-glibc2.12-x86_64.tar.gz -C /usr/local/
[root@mysql-2 ~]# mv /usr/local/mysql-5.7.36-linux-glibc2.12-x86_64 /usr/local/mysql
2.设置MySQL的环境变量
[root@mysql-2 ~]# vim /etc/profile
export MYSQL_HOME=/usr/local/mysql
export PATH=$MYSQL_HOME/bin:$PATH
export LD_LIBRARY_PATH=:/usr/local/mysql/lib
3.创建mysql用户
[root@mysql-2 ~]# groupadd -r mysql
[root@mysql-2 ~]# useradd -M -r -s /sbin/nologin -g mysql mysql
搭建第一个从库实例,端口号为3306,server_id为2,数据路径为/data/mysql3306。
1.创建3306从库的数据目录
[root@mysql-2 ~]# mkdir /data/mysql3306
[root@mysql-2 ~]# chown -R mysql.mysql /data/mysql3306/
2.准备3306从库的配置文件
[root@mysql-1 ~]# vim /etc/my3306.cnf
[mysqld]
user=mysql
port=3306
server_id=2 #每个MySQL数据库的server_id都设置成不同的
basedir=/usr/local/mysql
datadir=/data/mysql3306
log_bin=/data/mysql3306/mysql-bin
gtid-mode=on
enforce-gtid-consistency=true
socket=/data/mysql3306/mysql.sock
log_error=/data/mysql3306/mysql_err.log
character-set-server=utf8
[mysql]
socket=/data/mysql3306/mysql.sock
3.初始化3306从库
[root@mysql-2 ~]# mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql3306
4.准备服务管理脚本
[root@mysql-1 ~]# vim /etc/systemd/system/mysqld3306.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my3306.cnf
LimitNOFILE = 5000
5.启动数据库
[root@mysql-1 ~]# systemctl daemon-reload
[root@mysql-1 ~]# systemctl start mysqld3306
6.设置root密码
[root@mysql-2 ~]# mysqladmin -u root -P 3306 -S /data/mysql3306/mysql.sock password '123456'
7.登陆数据库
[root@mysql-2 ~]# mysql -uroot -p123456 -P3306 -S /data/mysql3306/mysql.sock
mysql>
搭建第一个从库实例,端口号为3306,server_id为2,数据路径为/data/mysql3306。
1.创建3307从库的数据目录
[root@mysql-2 ~]# mkdir /data/mysql3307
[root@mysql-2 ~]# chown -R mysql.mysql /data/mysql3307/
2.准备3307从库的配置文件
[root@mysql-1 ~]# vim /etc/my3307.cnf
[mysqld]
user=mysql
port=3307
server_id=3 #每个MySQL数据库的server_id都设置成不同的
basedir=/usr/local/mysql
datadir=/data/mysql3307
log_bin=/data/mysql3307/mysql-bin
gtid-mode=on
enforce-gtid-consistency=true
socket=/data/mysql3307/mysql.sock
log_error=/data/mysql3307/mysql_err.log
character-set-server=utf8
[mysql]
socket=/data/mysql3307/mysql.sock
3.初始化3307从库
[root@mysql-2 ~]# mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql3307
4.准备服务管理脚本
[root@mysql-1 ~]# vim /etc/systemd/system/mysqld3307.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my3307.cnf
LimitNOFILE = 5000
5.启动数据库
[root@mysql-1 ~]# systemctl daemon-reload
[root@mysql-1 ~]# systemctl start mysqld3307
6.设置root密码
[root@mysql-2 ~]# mysqladmin -u root -P 3307 -S /data/mysql3307/mysql.sock password '123456'
7.登陆数据库
[root@mysql-2 ~]# mysql -uroot -p123456 -P3307 -S /data/mysql3307/mysql.sock
mysql>
MySQL多个节点已经搭建完毕了,下面来查询每个实例的server_id。
MySQL数据库的多个实例节点已经搭建完成了,下面将这些搭建的数据库实例配置成主从复制集群(一主两从)。
IP | 主机名 | 端口 | 角色 |
---|---|---|---|
192.168.20.11 | mysql-1 | 3306 | 主库 |
192.168.20.12 | mysql-2 | 3306 | 从库 |
192.168.20.12 | mysql-2 | 3307 | 从库 |
配置MySQL主从复制集群的大致步骤:
主库已经开启了Binlog二进制日志,我们来查一下。
mysql> show variables like '%log_bin%';
+---------------------------------+-----------------------------+
| Variable_name | Value |
+---------------------------------+-----------------------------+
| log_bin | ON |
| log_bin_basename | /data/mysql/mysql-bin |
| log_bin_index | /data/mysql/mysql-bin.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| sql_log_bin | ON |
+---------------------------------+-----------------------------+
6 rows in set (0.01 sec)
从库复制主库数据,都是通过Binlog进行传输和恢复的。
mysql> grant replication slave on *.* to replicas@'192.168.20.%' identified by '123456';
如果是运行很久的主库,要升级为主从复制集群,那么建议将主库上的数据备份还原到从库上,避免从库一次性同步很多数据,浪费性能。
[root@mysql-1 ~]# mysqldump -uroot -p123456 -A --master-data=2 -R -E --triggers --single-transaction > all_db.sql
在192.168.20.12的两个从库中还原主库的备份数据。
1.将备份上传到从库的服务器上
[root@mysql-1 ~]# scp -rp all_db.sql root@192.168.20.12:/root
2.3306从库还原主库备份数据
[root@mysql-2 ~]# mysql -uroot -p123456 -P3306 -S /data/mysql3306/mysql.sock
mysql> set sql_log_bin=0;
mysql> source /root/all_db.sql;
3.3307从库还原主库备份数据
[root@mysql-2 ~]# mysql -uroot -p123456 -P3307 -S /data/mysql3307/mysql.sock
mysql> set sql_log_bin=0;
mysql> source /root/all_db.sql;
接下来我们需要配置从库连接主库的信息,包括让从库知道主库的IP、端口号、复制的用户密码、Binlog相关的信息。
1)获取主库Binlog的信息
从库是通过Binlog复制主库数据的,首先要获取主库Binlog日志的一些信息,包括主库的Binlog日志使用的是哪个、要从Binlog日志中的哪一个事件标识号处开始复制数据。
从Binlog的哪一个事件标识号处开始复制数据,尤为关键,因为主库可能运行的时间长了,也有很多的数据,一定要从合适的位置处开始复制数据,要不然也会产生很大的资源浪费。
我们是用主库头一天的全库备份,然后还原到从库上的,在主库的备份文件中就记录了从库应该从哪一个Binlog事件标识位处开始复制数据。
vim all_db.sql
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=452;
从备份文件中得知,我们应该让从库从主库Binlog事件标识位号452处,开始复制数据。
如果主库不是运行很久的,不需要备份数据还原到从库,而是直接搭建的主从复制集群,那么直接可以从主库状态信息那里,获取Binlog的标识位号,在配置主从时,一定要指对Binlog标识位号,否则主从将会失败。
mysql> show master status;
+------------------+----------+--------------+------------------+----------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+----------------------------------------+
| mysql-bin.000001 | 452 | | | 4f87bad8-fc67-11ec-be7b-005056b791aa:1 |
+------------------+----------+--------------+------------------+----------------------------------------+
1 row in set (0.00 sec)
2)配置从库连接主库进行复制的参数信息
配置从库连接主库进行复制的参数有很多,不过只需要记住这一条命令:help change master to
即可,在MySQL交互式下执行此命令可以打印出从库连接主库的所有配置参数。
1.3306从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3306 -S /data/mysql3306/mysql.sock
mysql> CHANGE MASTER TO
MASTER_HOST='192.168.20.11',
MASTER_USER='replicas',
MASTER_PASSWORD='123456',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=452,
MASTER_CONNECT_RETRY=10;
1.3307从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3307 -S /data/mysql3307/mysql.sock
mysql> CHANGE MASTER TO
MASTER_HOST='192.168.20.11',
MASTER_USER='replicas',
MASTER_PASSWORD='123456',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=452,
MASTER_CONNECT_RETRY=10;
参数解释:
参数 | 含义 |
---|---|
MASTER_HOST | 主库的地址 |
MASTER_USER | 主从复制的专用用户 |
MASTER_PASSWORD | 主从复制用户的密码 |
MASTER_PORT | 主库的端口号 |
MASTER_LOG_FILE | 主库的Binlog日志名 |
MASTER_LOG_POS | 从主库BInlog日志的哪一个标识位处开始复制 |
MASTER_CONNECT_RETRY | 主从连接失败的重试时间间隔 |
1.3306从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3306 -S /data/mysql3306/mysql.sock
mysql> start slave;
1.3307从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3307 -S /data/mysql3307/mysql.sock
mysql> start slave;
1.3306从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3306 -S /data/mysql3306/mysql.sock
mysql> show slave status\G;
1.3307从库配置
[root@mysql-2 ~]# mysql -uroot -p123456 -P3307 -S /data/mysql3307/mysql.sock
mysql> show slave status\G;
当每个从库的IO线程和SQL线程的状态都是Yes,就表示主从复制集群搭建完毕了。
MySQL主从配置过程中从库连接主库的配置写错了应该如何解决?
只要不执行start slave,再执行一次change master to会覆盖上一次配置。
1.停止主从复制
mysql>stop slave;
2.清空从库连接主库的信息
mysql> reset slave all;
3.重新设置
mysql> CHANGE MASTER TO
MASTER_HOST='192.168.20.11',
MASTER_USER='replicas',
MASTER_PASSWORD='123456',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=452,
MASTER_CONNECT_RETRY=10;