内容参考自官方文档 MySQL :: MySQL 8.4 Reference Manual :: 19 Replication

基于 Binlog 的复制

主从之前是通过二进制日志文件(Binary log, Binlog)进行同步的,源(数据库更改发生的地方,称为 source)的 MySQL 实例将更新和更改以“事件”的形式写入 Binlog。副本(称为 replica)的 MySQL 实例从源读取 Binlog,并在副本的本地数据库上执行其中记录的事件。

每个连接到 source 的 replica 都会请求一份 Binlog 的副本。**它是从源端拉取数据,而不是源端向副本推送数据。**每个 replica 都会记录每个 Binlog 的坐标,即 replica 知道某个日志哪些部分是被读取并处理过的。这意味着多个 replica 可以连接到 source,并执行同一 Binlog 的不同部分。由于 replica 控制此过程,因此可以独立连接和断开单个 replica,而不会影响 source 的操作。此外,由于每个 replica 都单独记录了 Binlog 坐标,因此 replica 在断开连接后可以重新连接并继续处理。

source 和每个 replica 都必须配置一个唯一的 ID,可以在配置文件中使用 server_id 控制。此外,每个 replica 还必须配置有关 source 主机名、Binlog 文件名以及在该文件中的位置的信息。这些详细信息可以通过在副本上使用 CHANGE REPLICATION SOURCE TO 语句在 MySQL 会话中进行控制。

基于 GTID 的复制

使用 GTID 同步的优势

  • 在启动新 replica 或故障转移到新 source 时,无需设置日志文件和文件中的位置;
  • 只要比较应用过的 GTID 就可以简单地确定源和副本是否一致;
  • GTID 是整个集群唯一 的,可以通过检查其 binlog 来确定任何 replica 上应用的任何事务的 source;
  • 一旦在某个服务器上提交了具有给定 GTID 的事务,该服务器将忽略任何具有相同 GTID 的后续事务,这有助于保证一致性。

GTID 的格式和存储

GTID 分配规则区分了客户端事务和复制事务,客户端事务是在 source 提交的,而复制事务是在replica 上复制的。当一个客户端事务在 source 提交时,如果该事务被写入 binlog,则会分配一个新的 GTID。MySQL 保证客户端事务的 GTID 是递增且没有间隔的数字。当然,如果一个客户端事务没有被写入 binlog(例如,因为事务被过滤掉,或者事务是只读的),则不会在 source 上分配 GTID。复制事务则仍旧使用在 source 上被分配好的 GTID。mysql.gtid_executed 这个表用于保存所有 GTID,不过那些存储在当前活动 binlog 中的事务除外。

一个 GTID 用一对坐标表示。

1
GTID = source_id:transaction_id

搭建一主一从环境

准备工作

使用 Docker Compose 搭建一主一从的 MySQL 8.4.x 集群。

Docker Compose 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
services:
source:
ports:
- 3306:3306
container_name: mysql-source
volumes:
- ./source/data:/var/lib/mysql
- ./source/conf:/etc/mysql/conf.d
environment:
MYSQL_ROOT_PASSWORD: root
TZ: Asia/Shanghai
image: mysql:8.4
networks:
- mysql
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-proot" ]
interval: 5s
timeout: 10s
retries: 10
replica:
ports:
- 3307:3307
image: mysql:8.4
container_name: mysql-replica
volumes:
- ./replica/data:/var/lib/mysql
- ./replica/conf:/etc/mysql/conf.d
depends_on:
source:
condition: service_healthy
environment:
MYSQL_ROOT_PASSWORD: root
TZ: Asia/Shanghai
networks:
- mysql
networks:
mysql:
driver: bridge

配置文件

source

source/conf/my.cnf 文件中,配置如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[mysqld]
# 端口
port=3306
# 唯一
server_id=1
# 只读关闭
read_only=OFF
# 启用 GTID
gtid_mode=ON
# 强制要求从库只能复制具有与主库一致的 GTID 链的事务,
# 以确保数据一致性。
enforce_gtid_consistency=ON
# binlog 日志名称
log_bin=source-binlog
# 二进制日志过期清理时间(一周)。默认值为0,表示不自动清理。
binlog_expire_logs_seconds=604800
# 开启
mysql_native_password=ON

replica

replica/conf/my.cnf 文件中,配置如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[mysqld]
# 保证唯一
server_id=2
# 只读(除了 super 账户)
read_only=ON
# 开启 GTID
gtid_mode=ON
enforce_gtid_consistency=ON
# 端口
port=3307
# binlog 文件名
log_bin=replica-binlog
# 中继日志
relay_log=replica-relay
# 密码插件
mysql_native_password=ON

重要参数解析

详细的参数可查阅 MySQL :: MySQL 8.4 Reference Manual :: 19.1.6 Replication and Binary Logging Options and Variables

enforce_gtid_consistency=ON

必须在启用基于 GTID 的复制之前将此变量设置为 ON。为保持数据一致性,仅允许执行可以使用 GTID 安全记录的语句。

relay_log=replica-relay

用于设置 relay log 的文件名。默认的 relay log 文件名称为主机名开头,类似 host_name-relay-bin。如果在设置 replica 后更改其主机名,并且使用默认的基于主机的文件名,则可能导致复制失败,出现 “Failed to open the relay log” 和 “Could not find target log during relay log initialization” 的错误。

启动新的 Docker 容器,data 目录下还有之前的 relay log,但是主机名变了,就会导致找不到 relay log 文件而报错。因此,需要设置固定的 relay log 文件名。

值得一提的是,relay_log 索引文件也有这个问题,但是如果已经指定了 relay_log,则指定的值也将用作索引文件的名称,就不需要再次指定索引文件名了。可以通过使用 relay_log_index 进行覆写。

mysql_native_password=ON

MySQL 8.4 默认使用 caching_sha2_password 插件,这就必须设置安全连接(MySQL :: MySQL 8.4 Reference Manual :: 19.3.1 Setting Up Replication to Use Encrypted Connections)。这里出于演示方便,就使用已经被弃用的 mysql_native_password 插件。

启动

1
docker compose up

会首先启动 source 实例,然后再启动 replica 实例。 这是因为配置了 depends_on,只有 source 实例启动后并检查到健康状态,replica 实例才会启动。

创建用于主从连接的用户

每个副本都使用 MySQL 用户名和密码连接到源,因此源上必须有一个副本可以使用的用户。用户名是在设置副本时通过 CHANGE REPLICATION SOURCE TO 语句的 SOURCE_USER 选项指定的,而且这个 SOURCE_USER 必须已被授予 REPLICATION SLAVE 权限。可以选择为每个副本创建不同的用户,或者使用相同的用户连接到源。

接下来就首先在源上创建这个用户。

1
2
3
CREATE USER 'replica'@'%' IDENTIFIED WITH mysql_native_password BY 'replica';
GRANT REPLICATION SLAVE ON *.* TO 'replica'@'%';
FLUSH PRIVILEGES;

启动复制

在 replica 实例上,使用如下语句启动复制。

1
2
3
4
5
6
7
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = 'mysql-source',
SOURCE_PORT = 3306,
SOURCE_USER = 'replica',
SOURCE_PASSWORD = 'replica',
SOURCE_AUTO_POSITION = 1;
START REPLICA;