一.主从同步流程
关于MySQL主从复制主要同步的是binlog日志,涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在从节点,如下图所示:
当主库数据发生变更时,写入本地Bin Log文件
从库IO线程发起dump主库Bin Log文件的请求
主库IO线程推送Bin Log文件到从库中
从库IO线程把Bin Log内容写入本地的Relay Log文件中
从库SQL线程读取Relay Log文件内容
从库SQL线程重新执行一遍SQL语句
二.实现主从一致的原理
主节点 binary log dump 线程
当从节点连接主节点时,主节点会创建一个log dump 线程,用于发送binlog的内容。在读取binlog中的操作时,此线程会对主节点上的binlog加锁,当读取完成,在发送给从节点之前,锁会被释放。
从节点I/O线程
当从节点上执行`start slave`命令之后,从节点会创建一个I/O线程用来连接主节点,请求主库中更新的binlog。I/O线程接收到主节点binlog dump 进程发来的更新之后,保存在本地relay-log(中继日志)中。
从节点SQL线程
SQL线程负责读取relay log中的内容,解析成具体的操作并执行,最终保证主从数据的一致性。
三.MySQL 主从复制模式
MySQL 主从复制默认是异步的模式。MySQL增删改操作会全部记录在binlog中,当slave节点连接master时,会主动从master处获取最新的bin log文件。并把bin log中的sql relay。
异步模式(mysql async-mode)
原理:客户端提交 COMMIT 之后主库,不需要等从库返回任何结果,而是直接将结果返回给客户端,这样做的好处是不会影响主库写的效率,但可能会存在主库宕机(就凉了),而 Binlog 还没有同步到从库的情况,也就是此时的主库和从库数据不一致。
这时候从从库中选择一个作为新主,那么新主则可能缺少原来主服务器中已提交的事务。所以,这种复制模式下的数据一致性是最弱的。
风险:一旦数据只写到了主库的binlog中还没来得急同步到从库时,主库挂了,从库就就会被强行提升为主库,就会造成数据的丢失。但高效。
半同步模式(mysql semi-sync)
同步模式:当主库执行完客户端提交的事务后,需要等到 所有从库也都执行完这一事务后,才返回给客户端执行成功。因为要等到所有从库都执行完,执行过程中会被阻塞,等待返回结果,所以性能上会有很严重的影响。
半同步复制模式:主库在执行完客户端提交的事务后,要等待 至少一个从库接收到binlog并将数据写入到relay log中才返回给客户端成功结果。半同步复制模式,比异步模式提高了数据的可用性,但是也产生了一定的性能延迟,最少要一个TCP/IP连接的往返时间。所以,半同步复制最好在低延时的网络中使用。
在master的dump线程去通知从库时,增加了一个ACK机制,也就是会确认从库是否收到事务的标志码,master的dump线程不但要发送binlog到从库,还有负责接收slave的ACK。当出现异常时,Slave没有ACK事务,出现等待超时的情况,那么将 自动降级为异步复制,直到异常修复后再自动变为半同步复制。
。
风险:
当事务在主库提交完后等待从库ACK的过程中,如果Master宕机了,这个时候就会有两种情况的问题:
事务还没发送到Slave上:若事务还没发送Slave上,客户端在收到失败结果后,会重新提交事务,因为重新提交的事务是在新的Master上执行的,所以会执行成功,后面若是之前的Master恢复后,会以Slave的身份加入到集群中,这个时候,之前的事务就会被执行两次,第一次是之前此台机器作为Master的时候执行的,第二次是做为Slave后从主库中同步过来的。【为什么客户端收到失败结果后会重新提交事务?事务不是已经提交了吗,只不过没有等到ACK。】
事务已经同步到Slave上:因为事务已经同步到Slave了,所以当客户端收到失败结果后,再次提交事务,你那么此事务就会再当前Slave机器上执行两次。
改进:
MySQL从5.7版本开始,增加了一种新的半同步方式。新的半同步方式的执行过程是将“Storage Commit”这一步移动到了“Write Slave dump”后面。这样保证了只有Slave的事务ACK后,才提交主库事务。【就是之前先提交commit再同步,现在是先保证同步了再commit】。MySQL从5.7.2版本开始,默认的半同步复制方式就是AFTER_SYNC方式,但这种同步方式可能会存在slave多数据的情况。
半同步模式不是mysql内置的,从mysql 5.5开始集成,需要master 和slave 安装插件开启半同步模式。
全同步模式
全同步模式是指主节点和从节点全部执行了commit并确认才会向客户端返回成功。
四.docker部署mysql主从
机器 | 端口 | 角色 |
10.x.x.121 | 3306 | 主 |
10.x.x.1 | 3306 | 从 |
1.linux基础环境配置
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭selinux
sed -i 's/enforcing/disabled/' /etc/selinux/config # 永久
setenforce 0 # 临时
# 关闭swap
swapoff -a # 临时
sed -ri 's/.*swap.*/#&/' /etc/fstab # 永久
# 将桥接的IPv4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system # 生效
# 时间同步
yum install ntpdate -y
ntpdate time.windows.com
2.安装docker环境(主从都要)
yum install -y wget
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repowget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum install -y docker-ce-20.10.7 docker-ce-cli-20.10.7 containerd.io-1.4.6
systemctl enable docker && systemctl start docker
docker --version
#配置镜像加速器
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [" https://afi5x6i2.mirror.aliyuncs.com "]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
3.配置及映射(mysql的数据目录挂载)
mysql配置文件容器中的位置/etc/mysql/mysql.conf.d/mysqld.cnf
mysql数据文件容器中的位置/var/lib/mysql
配置文件,提前编辑好我的配置文件位置/opt/docker/container/mysql/conf/
主配置文件 mysqld.cnf
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
#log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_general_ci
[client]
default-character-set=utf8
[mysqld]
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
## 设置server_id,同一局域网中需要唯一
server_id=101
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
### 开启二进制日志功能
log-bin=mysql-bin
### 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
### 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=row
### 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7
### 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
### 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
从配置文件 mysqld.cnf
不要将client的default-character-set=utf8移动位置,因为必须在server的配置文件下,否则启动失败,异常找不到server_id
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
#log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_general_ci
#[mysqld]
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
## 设置server_id,同一局域网中需要唯一
server_id=102
## 开启二进制日志功能,以备Slave作为其它数据库实例的Master时使用
log-bin=mysql-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=row
## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates=1
## slave设置为只读(具有super权限的用户除外)
read_only=1
[client]
default-character-set=utf8
4.容器启动和数据卷映射
#拉取镜像
docker pull mysql:5.7
#分别创建容器
docker run -id --name=mysql --net host -e MYSQL_ROOT_PASSWORD=mysql密码 -e TZ=Asia/Shanghai -v /opt/docker/container/mysql/conf/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf -v /opt/docker/container/mysql/data:/var/lib/mysql mysql:5.7 --port=3306
docker run -id --name=mysql -e MYSQL_ROOT_PASSWORD=mysql密码 -p 33506:3306 -e TZ=Asia/Shanghai -v /opt/docker/container/mysql/conf/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf -v /opt/docker/container/mysql/data:/var/lib/mysql mysql:5.7
#进入主mysql容器并登陆
docker exec -it mysql bash
mysql -uroot -p密码
#创建只读用户并且授权
CREATE USER 'slave'@'%' IDENTIFIED BY 'slave123@pdy6bObCqG0g';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
#查看当前日志位置,记录file和Position,稍后会用到
show master status;
#进入从mysql容器并登陆
docker exec -it mysql bash
mysql -uroot -pmysql密码
#加入主库 master_log_file和master_log_pos上面提到的稍后会用的数据,master_connect_retry重试时间间隔,单位s
change master to master_host='10.x.x.121', master_user='slave', master_password='slave123@pdy6bObCqG0g', master_port=3306, master_log_file='mysql-bin.000003', master_log_pos=617, master_connect_retry=10;
#查看从库状态 #会发现Slave_IO_Running和Slave_SQL_Running都是no,并没有开始同步
show slave status \G;
#开始同步
start slave;
#再次查看状态会发现变更为yes.