MySQL 主从同步原理与环境搭建(适用于 MySQL 8.4.x)
内容参考自官方文档 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 | services: |
配置文件
source
在 source/conf/my.cnf
文件中,配置如下。
1 | [mysqld] |
replica
在 replica/conf/my.cnf
文件中,配置如下。
1 | [mysqld] |
重要参数解析
详细的参数可查阅 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 | CREATE USER 'replica'@'%' IDENTIFIED WITH mysql_native_password BY 'replica'; |
启动复制
在 replica 实例上,使用如下语句启动复制。
1 | CHANGE REPLICATION SOURCE TO |