【MySQL】GTID主从复制【主从同步】

作用:主从复制在工作中可能会有两种形式:

传统AB复制,基于binlog日志 + pos点位实现复制

MySQL5.7及以后版本新增基于GTID的主从复制

相对于传统AB复制,基于GTID的主从复制在两方面比较灵活:

g:global

T:事务

配置灵活,不需要关心binlog及点位,直接配置,自动追踪

跳过异常,也比较灵活,简单操作就可以解决主从复制中的异常信息(SQL异常)

基于全局事务标识符(GTID)复制

GT=global transaction GTID = 服务器ID + 事务ID

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

事务标识符:每执行一次事务操作(增、删、改),系统都会给其定义一个唯一编号(很长的字符串)。

GTID:一个基于原始MySQL服务器生成的一个已经被成功执行的全局事务ID,它由服务器ID以及事务ID组合而成

全局事务ID不仅仅在原始服务器上唯一,在所有存在主从关系的MySQL服务器上也是唯一的

  • 幂等执行同一个 GTID 在单台 MySQL 实例只会被执行一次,自动跳过重复事务,杜绝主从重复执行造成数据错乱。
  • 复制启动方式变更 传统复制:依靠 MASTER_LOG_FILE + MASTER_LOG_POS 二进制日志文件 + 位置点启动同步。GTID 复制:配置 MASTER_AUTO_POSITION=1,自动获取同步起点,无需手动找日志位点。
  • 从库必须开启 binlog(关键区别)
  • 普通传统主从:从库只开 relay log,binlog 可关闭;
  • GTID 主从:从库必须开启 binlog,用来持久化已经执行完成的 GTID 事务号,保证故障切换时事务不会重复执行。

Master(主库):必须开启 binlog

Slave(从库):必须开启 relay log + 必须开启 binlog,用于保存已执行 GTID

GTID的优势

  • 更简单的实现 failover,不用以前那样在需要找位点(log_file 和 log_pos)。

  • 更简单的搭建主从复制。

  • 比传统的AB复制更加安全。

  • GTID 是连续的没有空洞的,保证数据的一致性,零丢失。

GTID工作原理

GTID:全局事务ID编号,server_uuid + 事务序号,不管单独MySQL服务器还是主从集群环境中,都是唯一的。

  1. 从库连接主库,把本机已经执行完的所有 GTID 集合(gtid_executed)发送给主库。
  2. 主库拿到从库的 GTID 集合,做集合运算:主库全部 GTID − 从库已执行 GTID = 待同步事务差集
  3. 校验:如果主库的 binlog 已经清理掉了差集对应的事务,直接报错,防止数据缺失。
  4. 主库自动定位到差集里第一条事务对应的 binlog 起始位置。
  5. 主库从该事务开始,持续把后续 binlog 推送给从库 IO 线程。
  6. 从库写入 relay log;SQL 线程执行事务。
  7. 关键特性:同一个 GTID 只会执行一次,自动跳过重复事务,保证幂等。

举例:

  • 主库已执行 GTID:1~100
  • 从库已执行 GTID:1~90
  • 差集:91~100主库自动找到 GTID=91 的位置,从此处开始推送 binlog。

GTID的配置与实现

作用:基于GTID实现主从复制(重点)

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

环境说明:

关闭防火墙+SELinux、配置IP与主机映射、时间同步、安装必备包

|----------------|-------------|-----------|
| IP | 主机名 | 角色 |
| 192.168.88.101 | mysql-node1 | master(主) |
| 192.168.88.102 | mysql-node2 | slave(从) |

环境准备

① 安装必备软件,如vim、wget、rsync

② 配置IP、主机名

③ 配置IP与主机映射 => /etc/hosts

④ 优化Linux系统

⑤ 时间同步

主从服务器都要安装一些依赖包

复制代码
dnf install vim wget rsync telnet net-tools -y

主从服务器都要设置一下主机名

复制代码
hostnamectl set-hostname mysql-node1
hostnamectl set-hostname mysql-node2

主机映射,两个服务器都要添加

复制代码
vim /etc/hosts

192.168.88.101   mysql-node1   node1
192.168.88.102   mysql-node2   node2

优化系统,一些防火墙设置

复制代码
sed  -i -r 's/SELINUX=[ep].*/SELINUX=disabled/g' /etc/selinux/config
setenforce 0
systemctl stop firewalld &> /dev/null
systemctl disable firewalld &> /dev/null
iptables -F
iptables -t nat -F
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT

时间同步

复制代码
dnf -y install chrony
systemctl enable chronyd --now

从服务器安装MySQL脚本

vim install-mysql8.sh

复制代码
#!/bin/bash

if rpm -q libaio &> /dev/null; then
    echo "libaio已安装,跳过安装"
else
    echo "开始安装依赖"
    dnf -y install libaio &> /dev/null
    if [ $? -ne 0 ];then
        echo "libaio安装失败"
        exit 1
    fi
fi

echo "进行解压操作"
if ls -l mysql-8.0.43-linux-glibc2.28-x86_64 &> /dev/null; then
    echo "已解压,跳过"
else
    if [ -f mysql-8.0.43-linux-glibc2.28-x86_64.tar.xz ]; then
        tar -xf mysql-8.0.43-linux-glibc2.28-x86_64.tar.xz
        ls -l mysql-8.0.43-linux-glibc2.28-x86_64
    fi
fi

echo "判断是否安装过MariaDB,进行清理"
rpm -qa | grep mariadb | xargs -r dnf remove -y
if [ -f /etc/my.cnf ]; then
        rm -rf /etc/my.cnf
fi

id mysql &> /dev/null
[ $? -ne 0 ] && useradd -r -s /sbin/nologin mysql

rm -rf /export/server
mkdir -p /export/server
cp -r mysql-8.0.43-linux-glibc2.28-x86_64 /export/server/mysql
chown -R mysql:mysql /export/server/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

bin/mysql_ssl_rsa_setup --datadir=/export/server/mysql/data &> /dev/null

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

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"

echo 'export PATH=$PATH:/export/server/mysql/bin' >> /etc/profile
source /etc/profile
mysql -V
echo "MySQL8安装成功,安装路径:/export/server/mysql,数据库初始密码:123456"

注意:执行完install-mysql8.sh安装脚本后,需要在Linux命令行执行source /etc/profile

初始化数据库(强烈建议)

建议把从服务器的data删除,然后重新同步

如果/etc/my.cnf有传统主从的配置,那么就要先删除对应的配置。

初始化数据库

复制代码
systemctl stop mysqld
rm -rf /export/server/mysql/data/*
rm -rf /tmp/mysqld.log
/export/server/mysql/bin/mysqld --initialize --user=mysql --basedir=/export/server/mysql &>/tmp/mysqld.log
grep password /tmp/mysqld.log | awk '{print $NF}'

mysqld --user=mysql --skip-grant-tables --skip-networking &

mysql

FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
FLUSH PRIVILEGES;
EXIT;

pkill mysqld
systemctl start mysqld
systemctl status mysqld --no-pager

mysql -uroot -p'123456'

rsync 同步 data 目录与删除 auto.cnf 文件

同步

复制代码
# 1、停止 master服务器 上的 MySQL 服务(可选)
systemctl stop mysqld

# 2、在 master 上执行 rsync 同步 data 目录(可选)
rsync -av --delete /export/server/mysql/data/ mysql-node2:/export/server/mysql/data/

# 3、在 master 或 slave 删除 auto.cnf 文件
rm -f /export/server/mysql/data/auto.cnf

修改配置文件/etc/my.cnf

修改master

复制代码
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=101

# 字符集
character_set_server=utf8mb4
collation-server=utf8mb4_unicode_ci

# GTID 配置
gtid_mode=ON
enforce_gtid_consistency=ON
log_slave_updates=ON
binlog_format=ROW
binlog_row_image=FULL
sync_binlog=1
expire_logs_days=7

# 复制过滤:不同步的系统库
binlog-ignore-db=information_schema
binlog-ignore-db=mysql
binlog-ignore-db=performance_schema
binlog-ignore-db=sys

# InnoDB 优化
default_storage_engine=InnoDB
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
innodb_log_buffer_size=64M
innodb_flush_log_at_trx_commit=1
innodb_file_per_table=1

# 连接配置
max_connections=500
max_connect_errors=1000000
table_open_cache=2000

# 慢查询日志
slow_query_log=1
slow_query_log_file=/export/server/mysql/slow.log
long_query_time=1

[client]
socket=/tmp/mysql.sock
EOF

修改slave

复制代码
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
relay-log-index=/export/server/mysql/data/relaylog.index
server-id=102

# 字符集
character_set_server=utf8mb4
collation-server=utf8mb4_unicode_ci

# GTID 配置
gtid_mode=ON
enforce_gtid_consistency=ON
log_slave_updates=ON
binlog_format=ROW
binlog_row_image=FULL
sync_binlog=1
expire_logs_days=7

# 复制过滤:不同步的系统库
replicate-wild-ignore-table=mysql.%
replicate-wild-ignore-table=information_schema.%
replicate-wild-ignore-table=performance_schema.%
replicate-wild-ignore-table=sys.%

# 从库只读
read_only=ON
super_read_only=ON
skip-slave-start=1   # 避免重启时自动启动复制线程,需手动控制

# 复制优化
relay_log_recovery=1
sync_relay_log=0
sync_relay_log_info=0
sync_master_info=0

# InnoDB 优化(适合从库,查询压力大)
default_storage_engine=InnoDB
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
innodb_log_buffer_size=64M
innodb_flush_log_at_trx_commit=2   # 从库可设置为 2,提高复制与查询性能
innodb_file_per_table=1

# 连接配置
max_connections=500
max_connect_errors=1000000
table_open_cache=2000

# 慢查询日志(从库常用于报表分析)
slow_query_log=1
slow_query_log_file=/export/server/mysql/slow.log
long_query_time=1

[client]
socket=/tmp/mysql.sock
EOF

上面的从库只读等于

复制代码
SET @@GLOBAL.read_only = ON;

锁表也差不多(flush tables with read lock;)(unlock tables;)

master节点/slave节点,优化配置,重启MySQL

复制代码
# master 主服务器
touch /export/server/mysql/master.err
chown -Rf mysql:mysql /export/server/mysql
systemctl restart mysqld
systemctl status mysqld --no-pager

# slave 从服务器
touch /export/server/mysql/slave.err
chown -Rf mysql:mysql /export/server/mysql
systemctl restart mysqld
systemctl status mysqld --no-pager

创建账号

主服务器创建账号

复制代码
CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
flush privileges;

启动同步

从库执行操作(需要调整为自己master服务器信息

复制代码
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = host,
SOURCE_PORT = port,
SOURCE_USER = user,
SOURCE_PASSWORD = password,
SOURCE_AUTO_POSITION = 1;

SOURCE_AUTO_POSITION = 1: 这告诉从服务器使用自动位置跟踪功能,以便它可以自动从主服务器获取最新的二进制日志事件,而无需手动指定位置。

开启从库复制

复制代码
start replica;

查看从库复制状态

复制代码
show slave status\G

测试主从复制结果

主库中加入数据

复制代码
create database if not exists db_itheima;
show databases;
use db_itheima;

create table if not exists students(
        id int primary key,
        name varchar(20)
) default charset=utf8;

insert into students values (10, 'lisa');
insert into students values (11, 'Rose');

select * from students;
show databases;

在从库查看数据

复制代码
show databases;
use db_itheima;
show tables;
select * from students;

从库中查看复制状态,这张表记录了本机已经执行过的所有 GTID 事务区间

复制代码
select * from mysql.gtid_executed;