一 核心概念
1、什么是主从复制
主从复制可以实现将数据从一台数据库服务器(master)复制到一台到多台数据库服务器(slave)
默认情况下,属于异步复制,所以无需维持长连接
解决问题:① 数据实时备份 ② 缓解服务器压力(读操作可以分散到slave服务器)=> MyCAT(读写分离软件)
简单来说,master将数据库的改变写入二进制日志,slave同步这些二进制日志,并根据这些二进制日志进行数据重演操作,实现数据异步同
2、MySQL复制原理

binlog 二进制日志, relaylog 重写日志(负责把主服务器的 DML 在 slave 服务器重写执行一遍)
binlog 保存了 用户对数据库的增删改事务操作 ( SQL 语句)、 relaylog 重写日志 (中继日志), 当从服务器从主服务器拉取到二进制日志数据时,会首先写入到 relaylog 重写日志中 。
mysqldump --single-transaction --master-data
详细描述:
前提:主服务器开启binlog二进制日志,从服务器开启relaylog中继日志。
① slave端的IO线程发送请求给master端的binlog dump线程
② master端binlog dump线程获取二进制日志信息(文件名和位置信息)发送给slave端的IO线程
③ salve端IO线程获取到的内容依次写到slave端relay log里,并把master端的bin-log文件名和位置记录到master.info里
④ salve端的SQL线程,检测到relay log中内容更新,就会解析relay log里更新的内容,并执行这些操作,从而达到和master数据一致
主从复制三要素:binlog、relaylog、两个线程
- IO 线程:从库 → 连主库 Binlog Dump → 拉 binlog → 写入本地 relaylog,记录主库位点
- SQL 线程:读本地 relaylog → 回放事务 → 记录 relay 执行位点
- binlog:主库原始变更日志;relaylog:从库中转缓存日志
|------------------------|
| master:主服务器;slave:从服务器 |
|-------------------------------------------------|
| 注:主从复制也是备份的一种,属于在线热备。到这里就学过3种备份了:逻辑备份、物理备份、在线热备 |
3、同步 vs 异步(面试常问)
| 方式 | 怎么工作 | 优点 | 缺点 |
|---|---|---|---|
| 同步复制 | 主库写数据时,从库必须马上同步完,主库才能继续下一个事务 | 两端数据高度一致 | 阻塞主库事务,主库被拖慢 |
| 异步复制 | 主库写完就走,从库空闲时自己来拉 | 不阻塞主库业务 | 可能出现主从延迟 |
4、两个关键日志(先记住这两个名字)
| 日志 | 住哪儿 | 干什么的 |
|---|---|---|
| binlog(二进制日志) | 主库 | 记录用户对数据库的增删改操作(SQL 语句) |
| relay log(中继日志/重写日志) | 从库 | 从主库拉来的 binlog 先存这儿,然后在这上面"重演"一遍 |
大白话:binlog 是主库的"账本",relay log 是从库抄回来的"副本账本",从库照着副本再抄一遍到自己库里。
5、复制原理四步走(必背)
前提:主库开 binlog,从库开 relay log。
1、从库 IO 线程读取本地记录的位点,连接主库 Binlog Dump 线程,告知同步起点
【我上次收到主库 binlog.000004,位置 1207,接下来从这里往后给我数据】
2、Binlog Dump 定位到 binlog 对应位置,持续向后推送 binlog 事件
【如果这个位置后面已经有现成 binlog 数据:立刻读取、发送给从库 IO 线程;
如果这个位置后面暂时没有新写入的 binlog:Binlog Dump 线程阻塞等待(长连接挂着),主库一旦有新 DML/DDL 提交写入 binlog,马上把新事件推送给从库 IO 线程。】
3、IO 线程接收事件,写入 relaylog,更新 IO 接收位点
【这个 pos 之前的日志,我已经成功接收落盘到 relaylog 了,下次断连重连,就从这个 pos 继续。】
4、SQL 线程读取 relaylog,回放执行事务,更新回放位点
【从库 SQL 线程 持续监控 relaylog 有没有新增事件,SQL 线程按顺序读取 relaylog 里的事件,解析并在从库本地执行(DML/DDL 事务回放,事务回放成功后,更新 mysql.slave_relay_log_info:记录已经回放执行到哪个主库位点,已经回放完成、不需要再用的 relaylog,MySQL 自动清理】
5、循环往复,保持主从数据一致
记忆口诀:IO 线程负责" 搬" ,SQL 线程负责" 干" 。
后面排查故障你会反复看到这两个线程的状态,所以现在一定要分清:
- Slave_IO_Running: Yes → 成功连上主库并且下载了日志
- Slave_SQL_Running: Yes → 成功执行了日志里的 SQL 语句
- Seconds_Behind_Master: 0 → 主从延迟秒数,0 表示最佳状态,无延迟
**二、传统主从复制(**AB 复制)设计
1. MySQL****主从复制环境准备
传统AB复制架构(M-S),说明:mysql数据库,版本为8.0.40
环境说明:
|----------------|-------------|-----------|
| IP | 主机名 | 角色 |
| 192.168.30.111 | node1.it.cn | master(主) |
| 192.168.30.112 | node2.it.cn | slave(从) |
安装前准备:① 配置IP、主机名 ② 配置IP与主机映射 => /etc/hosts ③ 关闭防火墙与SELinux ④ 时间同步 ⑤ 安装必备软件,如vim、wget、rsync
设置主机名称
hostnamectl set-hostname node1.itcast.cn
hostnamectl set-hostname node2.itcast.cn
配置IP与主机映射
vim /etc/hosts
尾部追加如下内容
192.168.88.101 node1 node1.itcast.cn
192.168.88.102 node2 node2.itcast.cn
关闭防火墙与SELinux
systemctl stop firewalld
systemctl disable firewalld
setenforce 0
vim /etc/selinux/config
安装一些依赖软件(系统必备软件)
yum install vim wget rsync -y
**2.**搭建主从复制思路
- master、slave必须安装相同版本的mysql数据库软件
- master端必须开启二进制日志;slave端必须开启relay log中继日志
- master端和slave端的server-id号不能一致 => my.cnf => server-id = 10
- 同步master端数据之前,要删除data数据目录下的auto.cnf文件 => uuid编号(每个mysql实例都是唯一的)
master => mysql(uuid编号 => 数据库初始化自动生成) => /export/server/mysql/data/auto.cnf
slave数据目录是把master中的data同步过来,导致两个MySQL公用同一个uuid编号
5.slave端配置向master来同步数据
- master端必须创建一个复制用户
- 保证master和slave端初始数据一致
- 配置主从复制(slave端)
3. MySQL8****主从复制实践
3.1****配置 Master 主服务器
既要安装MySQL软件,也要初始化MySQL数据库
下面是master安装脚本 需要脚本同目录下有mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz安装包
bash
#!/bin/bash
#1.安装依赖软件
echo "正在安装依赖软件..."
yum -y install libaio &> /dev/null
if [ $? -ne 0 ];then
echo "libaio安装失败"
exit 1
fi
#2.判断是否有压缩包,如果有,则执行解压缩操作
echo "正在判断是否有压缩包,如果有进行解压缩操作..."
if [ -f mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz ]; then
tar -xf mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz
ls -l mysql-8.0.40-linux-glibc2.17-x86_64
fi
#3.判断系统中是否安装过mariadb软件,如果有对其进行卸载操作
echo "正在判断系统中是否安装过mariadb软件,如果有对其进行卸载操作..."
rpm -qa | grep mariadb | xargs -r yum remove -y
[ -f /etc/my.cnf ] && rm -rf /etc/my.cnf
#4.创建mysql系统账号
id mysql &> /dev/null
[ $? -ne 0 ] && useradd -r -s /sbin/nologin mysql
#5.创建/export/server目录,然后移动mysql压缩包解压后的文件到/export/server目录下
rm -rf /export/server
mkdir -p /export/server
mv mysql-8.0.40-linux-glibc2.17-x86_64 /export/server/mysql
#6.进入mysql目录,对其进行初始化操作
echo "正在进入mysql目录,对其进行初始化操作..."
cd /export/server/mysql
bin/mysqld --initialize --user=mysql --basedir=/export/server/mysql --datadir=/export/server/mysql/data 2>&1 | tee /tmp/mysqld.log | grep password | awk '{print $NF}' > /tmp/mysql_temp_password.txt
#7.设置ssl加密传输连接
bin/mysql_ssl_rsa_setup --datadir=/export/server/mysql/data &> /dev/null
#8.设置my.cnf与mysqld.service文件
echo "正在设置my.cnf与mysqld.service文件..."
cat > /etc/my.cnf <<EOF
[mysqld]
port=3306
basedir=/export/server/mysql
datadir=/export/server/mysql/data
socket=/tmp/mysql.sock
character_set_server=utf8
collation-server=utf8_unicode_ci
EOF
cat > /etc/systemd/system/mysqld.service <<EOF
[Unit]
Description=MySQL Server
After=network.target
[Service]
User=mysql
Group=mysql
Type=forking
# MySQL 执行命令及路径
ExecStart=/export/server/mysql/bin/mysqld --daemonize --pid-file=/export/server/mysql/data/mysqld.pid
ExecStop=/export/server/mysql/bin/mysqladmin --defaults-file=/export/server/mysql/my.cnf shutdown
# Ensure MySQL has sufficient time to start up
TimeoutSec=600
# PID 文件路径
PIDFile=/export/server/mysql/data/mysqld.pid
# Enable these options to auto-restart the service if it crashes
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
#9.刷新后台服务,然后启动mysqld
echo "正在刷新后台服务,然后启动mysqld..."
systemctl daemon-reload
systemctl start mysqld
systemctl enable mysqld
#10.重置mysql管理员密码为123456
echo "正在重置mysql管理员密码..."
cd /export/server/mysql
temp_password=`cat /tmp/mysql_temp_password.txt`
bin/mysqladmin -uroot password '123456' -p$temp_password
#11.把mysql的bin目录添加到环境变量中
echo 'export PATH=$PATH:/export/server/mysql/bin' >> /etc/profile
source /etc/profile
#12.解决mysql客户端首次无法登录问题
[ ! -f /lib64/libncurses.so.5 ] && ln -s /lib64/libncurses.so.6 /lib64/libncurses.so.5
[ ! -f /lib64/libtinfo.so.5 ] && ln -s /lib64/libtinfo.so.6 /lib64/libtinfo.so.5
#13.弹出提示,MySQL安装成功
echo "MySQL安装成功,软件安装路径:/export/server/mysql,数据库初始密码:123456!"
3.2****配置 Slave 从服务器
只需要安装MySQL软件,不需要初始化也不需要启动MySQL软件。
bash
#!/bin/bash
#1.安装依赖软件
echo "正在安装依赖软件..."
yum -y install libaio &> /dev/null
if [ $? -ne 0 ];then
echo "libaio安装失败"
exit 1
fi
#2.判断是否有压缩包,如果有,则执行解压缩操作
echo "正在判断是否有压缩包,如果有进行解压缩操作..."
if [ -f mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz ]; then
tar -xf mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz
ls -l mysql-8.0.40-linux-glibc2.17-x86_64
fi
#3.判断系统中是否安装过mariadb软件,如果有对其进行卸载操作
echo "正在判断系统中是否安装过mariadb软件,如果有对其进行卸载操作..."
rpm -qa | grep mariadb | xargs -r yum remove -y
[ -f /etc/my.cnf ] && rm -rf /etc/my.cnf
#4.创建mysql系统账号
id mysql &> /dev/null
[ $? -ne 0 ] && useradd -r -s /sbin/nologin mysql
#5.创建/export/server目录,然后移动mysql压缩包解压后的文件到/export/server目录下
rm -rf /export/server
mkdir -p /export/server
mv mysql-8.0.40-linux-glibc2.17-x86_64 /export/server/mysql
#6.进入mysql目录,对其进行初始化操作
echo "正在进入mysql目录,从服务器无需进行初始化操作..."
cd /export/server/mysql
#7.设置ssl加密传输连接
bin/mysql_ssl_rsa_setup --datadir=/export/server/mysql/data &> /dev/null
#8.设置my.cnf与mysqld.service文件
echo "正在设置my.cnf与mysqld.service文件..."
cat > /etc/my.cnf <<EOF
[mysqld]
port=3306
basedir=/export/server/mysql
datadir=/export/server/mysql/data
socket=/tmp/mysql.sock
character_set_server=utf8
collation-server=utf8_unicode_ci
EOF
cat > /etc/systemd/system/mysqld.service <<EOF
[Unit]
Description=MySQL Server
After=network.target
[Service]
User=mysql
Group=mysql
Type=forking
# MySQL 执行命令及路径
ExecStart=/export/server/mysql/bin/mysqld --daemonize --pid-file=/export/server/mysql/data/mysqld.pid
ExecStop=/export/server/mysql/bin/mysqladmin --defaults-file=/export/server/mysql/my.cnf shutdown
# Ensure MySQL has sufficient time to start up
TimeoutSec=600
# PID 文件路径
PIDFile=/export/server/mysql/data/mysqld.pid
# Enable these options to auto-restart the service if it crashes
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
#9.刷新后台服务,然后启动mysqld
echo "正在刷新后台服务,暂不启动mysqld..."
systemctl daemon-reload
systemctl enable mysqld
#10.把mysql的bin目录添加到环境变量中
echo 'export PATH=$PATH:/export/server/mysql/bin' >> /etc/profile
source /etc/profile
#11.解决mysql客户端首次无法登录问题
[ ! -f /lib64/libncurses.so.5 ] && ln -s /lib64/libncurses.so.6 /lib64/libncurses.so.5
[ ! -f /lib64/libtinfo.so.5 ] && ln -s /lib64/libtinfo.so.6 /lib64/libtinfo.so.5
#13.弹出提示,MySQL安装成功
echo "MySQL安装成功,软件安装路径:/export/server/mysql,数据库暂未初始化,数据库暂未启动!"
注意:暂时不需要初始化数据库文件,只是安装好了和master相同版本的mysql数据库软件;后面向master来同步所有数据。
master:从0-1安装MySQL,需要初始化,需要start启动服务!
slave:从0-1安装,但是又和master有所不同,不需要初始化,因为数据来源于master,不需要启动mysqld,没有数据目录本身也无法启动!
注意:以上两个脚本执行都必须通过++source mysql8.sh++
问题:./mysql8.sh、sh/bash mysql8.sh 与 source mysql8.sh执行有何不同?
./mysql8.sh、sh/bash mysql8.sh都代表直接运行脚本,这两种方式运行脚本都会产生一个子进程,程序所在终端(主进程),两者之间相互独立,子进程环境变量无法影响父进程中的环境变量。
source执行脚本时,虽然也会产生一个子进程,但是不仅会影响子进程中的环境变量,也会影响父进程中的环境变量。
3.3****修改 MySQL 主从配置(核心)
master服务器 => my.cnf
实际添加了两行
log-error=/export/server/mysql/master.err
log-bin=/export/server/mysql/data/binlog
bash
cat > /etc/my.cnf <<EOF
[mysqld]
basedir=/export/server/mysql
datadir=/export/server/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/export/server/mysql/master.err
log-bin=/export/server/mysql/data/binlog
server-id=10
character_set_server=utf8mb4
collation-server=utf8mb4_unicode_ci
EOF
| 配置项 | 中文说明 |
|---|---|
| basedir=/export/server/mysql | MySQL 软件安装目录 |
| datadir=/export/server/mysql/data | 数据存放目录 |
| socket=/tmp/mysql.sock | 本地通信的 socket 文件路径 |
| port=3306 | 监听端口 |
| log-error=/export/server/mysql/master.err | 错误日志路径(排错全靠它) |
| log-bin=/export/server/mysql/data/binlog | 开启二进制日志,并指定存放路径和文件名前缀 |
| server-id=10 | 服务器唯一 ID,主从必须不同(主=10) |
| character_set_server=utf8mb4 | 服务端默认字符集 |
| collation-server=utf8mb4_unicode_ci | 排序规则 |
master主服务器配置完成后,先在主服务器端为其创建一个master.err的日志文件并授权
bash
touch /export/server/mysql/master.err
chown mysql.mysql /export/server/mysql/master.err
systemctl restart mysqld
slave服务器 => my.cnf => 开启了relaylog(重写日志 => 把主服务器binlog重写)
主要是添加
log-error=/export/server/mysql/slave.err
log-bin=/export/server/mysql/data/binlog
relay-log=/export/server/mysql/data/relaylog 【slave特有的中继日志】
bash
cat > /etc/my.cnf <<EOF
[mysqld]
basedir=/export/server/mysql
datadir=/export/server/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/export/server/mysql/slave.err
log-bin=/export/server/mysql/data/binlog
relay-log=/export/server/mysql/data/relaylog
server-id=20
character_set_server=utf8mb4
collation-server=utf8mb4_unicode_ci
EOF
从服务器配置完成后,也需要提前创建slave.err错误日志文件
bash
touch /export/server/mysql/slave.err
chown mysql.mysql /export/server/mysql/slave.err
3.4****启动 Master 主服务器并创建同步账号
从库要来主库拉数据,得有个账号。这个账号是在 master 上创建、给 slave 用的。
| 命令 | 中文说明 |
|---|---|
| CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123'; | 创建用户 slave;'%' 表示允许从任意 IP 连接;IDENTIFIED WITH mysql_native_password 是用旧版密码认证方式,为了兼容 Navicat、DataGrip 等第三方工具(MySQL 8 默认用新的 caching_sha2_password,老工具连不上) |
| GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%'; | 授予复制权限。REPLICATION SLAVE 让从库能读取主库二进制日志;REPLICATION CLIENT 让从库能查看主库状态;ON *.* 表示所有库所有表 replication slave replication sclient |
为什么要这两个权限? 目的就是让从库能读取二进制日志、并拉取日志中的数据信息。
3.5 查看主库状态(记下这两个值)
| 命令 | 中文说明 |
|---|---|
| show master status; | 查看主库当前 binlog 写到哪儿了 |
返回结果长这样:
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
|---|---|---|---|---|
| binlog.000003 | 682 |
务必记录下 File 和 Position 的值 ,并且记下之后不要再对主库做任何写操作------因为任何写操作都会让 Position 往后跑,你记下的值就失效了
3.6 把 master 的数据目录同步到 slave
这一步的目标是让两边初始数据一致。流程是:停主库 → 删 auto.cnf → rsync 同步 → 两端启动。
| 命令 | 中文说明 |
|---|---|
| systemctl stop mysqld | 先在 master 上停掉 MySQL(保证同步时数据不再变动) |
| cd /export/server/mysql | 进入 MySQL 安装目录 |
| rm -f data/auto.cnf | 删除 auto.cnf(里面存着数据库实例的 UUID,是唯一标识,不删会导致主从 UUID 冲突) |
| rsync -av /export/server/mysql/data node2:/export/server/mysql/ | 把主库 data 目录同步到 node2。-a 表示归档模式(保留权限、时间等所有属性),-v 表示显示详细过程 |
| chown -R mysql.mysql /export/server/mysql | 在 slave 上执行,-R 递归修改整个目录属主为 mysql(同步过来的文件属主可能不对,不改启动会报权限错) |
启动master和slave数据库
bash
systemctl start mysqld
注意:我们启动master和slave机器上的mysqld时,都容易出现启动不了的情况,遇到这种情况不要急,一定先要.err错误日志 => 只要环境问题,通过日志几乎100%可以解决
新知识点 :早期都用 scp 传文件(底层是 SSH 协议),但 scp 效率较低。现在推荐用 rsync 做 Linux 之间的数据同步,rsync -av 是标配用法,而且支持增量同步。
3.7 在 slave 端建立主从关系(最关键的一段)
先在主库加锁,防止配置过程中又有新数据写进来导致两边不一致:
bash
mysql> flush tables with read lock;
查看当前数据库的二进制日志写到什么位置
sql
mysql> show master status;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000004 | 1207| | | |
+---------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
slave服务器执行以下操作:配置主从信息,但还未开始真正同步。
slave实现同步到master => change replication source
sql
mysql> CHANGE REPLICATION SOURCE TO
SOURCE_HOST='192.168.88.101',
SOURCE_USER='slave',
SOURCE_PASSWORD='123',
SOURCE_LOG_FILE='binlog.000004',
SOURCE_LOG_POS=1207;
在slave服务器上启动同步
sql
mysql> start slave;
mysql> show slave status\G
......
Slave_IO_Running: Yes 代表成功连接到master并且下载日志
Slave_SQL_Running: Yes 代表成功执行日志中的SQL语句
Seconds_Behind_Master: 0 代表主从延迟的秒数,如果为0,代表状态最佳,主从没有延迟
;与\G都代表SQL的结尾,有所不同在于分号是横向展示,而\G把每一列纵向显示,适合大数据展示!
回到master主服务器,在mysql里面,进行解锁操作
sql
mysql> unlock tables;
3.8 测试主从复制结果
master中创建数据库、数据表并插入数据
sql
create database db_itheima;
use db_itheima;
create table students1(
id int primary key,
name varchar(20)
) default charset=utf8;
insert into students values (1, 'Tom');
insert into students values (2, 'Rose');
在slave端验证:
sql
mysql> show databases;
mysql> use db_itheima;
mysql> show tables;
mysql> select * from students;
特别注意:一旦两者配置为主从以后,主master节点既可以读数据也可以写数据,但是一定一定要注意slave服务器只能读数据,不能写数据,一旦写入,集群主从马上报错!!!