使用容器实现 MariaDB 半同步复制

在博客文章使用容器实现 MariaDB 复制中,我们展示了如何使用 Docker 容器在 MariaDB 中正确复制数据。
我们使用了标准或异步或延迟复制。

这种复制类型的问题在于,如果主库发生故障,可能会导致数据丢失。在主库上提交的事务不会发送到副本,并且副本也不会提交更改。在这种情况下,从主库到副本的故障转移可能导致与主库相比缺失事务。

为了克服这类错误,有半同步复制,它自 MariaDB 10.4 版本起集成,还有完全同步复制,我们计划最终将其实现为MDEV-19140。另一种选择是使用 Galera。
在完全同步复制中,所有副本都必须验证一个事务,然后主库才会返回执行该事务的会话;而在半同步复制中,至少一个副本需要确认事务事件已成功传输。与完全同步复制相比,这提供了更快的复制速度。
与异步复制相比,半同步复制提供了改进的数据完整性,因为当主库收到至少一个副本的确认并提交更改时,我们可以确信数据至少存在于 2 个地方。

在本博客中,我们首先探讨
- 如何通过示例演示设置半同步复制。
之后,对于更感兴趣的读者,我们将以图表的形式可视化以下内容
- 标准复制配置
- 标准复制事务示例
- 半同步复制配置
- 半同步复制事务示例
1. 使用容器的半同步演示示例
- 在本示例中,我们将使用无状态应用程序,仅作为概念验证。
- 在我们的mariadb-docker GitHub仓库中有一个Docker compose 文件。
- 在主库上启用半同步的关键是设置
--rpl_semi_sync_master_enabled
,在副本上启用半同步是设置--rpl_semi_sync_slave_enabled
。
- 在主库上启用半同步的关键是设置
- 我们将使用 GTID(它们是自动启用的),并在下面的示例中展示它们如何变化。
- 这样,复制将从最后复制到副本的 GTID 的位置开始(从系统变量
gtid_slave_pos
中查看)。
- 这样,复制将从最后复制到副本的 GTID 的位置开始(从系统变量
- 本节包含以下内容
1.1 创建和检查容器
1.2 检查主库
1.3 检查二进制日志
1.4 检查副本
1.5 开始复制
1.1 创建和检查容器
- 启动容器
$ docker compose -f compose-replication-semisync.yml up
- 检查容器状态(确保它们处于健康状态)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ddb1fa67fef mariadb:lts "docker-entrypoint.s…" 2 minutes ago Up 2 minutes (healthy) 3306/tcp mariadb-replica-2
dca7a79526ca mariadb:lts "docker-entrypoint.s…" 2 minutes ago Up 2 minutes (healthy) 3306/tcp mariadb-replica-1
addabd306bb4 mariadb:lts "docker-entrypoint.s…" 2 minutes ago Up 2 minutes (healthy) 3306/tcp mariadb-primary
- 检查日志

1.2 检查主库
- 使用
mariadb
客户端检查半同步是否启用
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "select @@rpl_semi_sync_master_enabled;"
+--------------------------------+
| @@rpl_semi_sync_master_enabled |
+--------------------------------+
| 1 |
+--------------------------------+
- 检查 rpl_semi_sync_master_timeout 变量(默认为 10 [s])。当这段时间过去,主库未从副本获得任何确认时,它会切换回异步复制。
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "select @@rpl_semi_sync_master_timeout;"
+--------------------------------+
| @@rpl_semi_sync_master_timeout |
+--------------------------------+
| 10000 |
+--------------------------------+
- 检查主库状态(同时检查二进制日志中的位置和二进制日志的名称)。
$ docker exec mariadb-primary mariadb -uroot -psecret -e "show master status\G;"
*************************** 1. row ***************************
File: mariadb-bin.000002
Position: 344
Binlog_Do_DB:
Binlog_Ignore_DB:
- 检查用于复制的数据库
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show databases like '%test%'";
+-------------------+
| Database (%test%) |
+-------------------+
| testdb |
+-------------------+
1.3 检查二进制日志
- 有两个二进制日志和一个索引文件
$ docker exec --workdir /var/lib/mysql mariadb-primary bash -c "ls maria*"
mariadb-bin.000001
mariadb-bin.000002
mariadb-bin.index
- 通过
mariadb
客户端也可以看到同样的信息
$ docker exec mariadb-primary mariadb -uroot -psecret -e "show binary logs\G;"
*************************** 1. row ***************************
Log_name: mariadb-bin.000001
File_size: 1166
*************************** 2. row ***************************
Log_name: mariadb-bin.000002
File_size: 344
你可以使用 mariadb-binlog
客户端自由地检查二进制日志。
- 全新启动示例(注释将在后面的示例中排除)
$ docker exec mariadb-primary mariadb-binlog /var/lib/mysql/mariadb-bin.000002
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#231123 12:21:47 server id 1 end_log_pos 256 CRC32 0x21866123 Start: binlog v 4, server v 10.11.6-MariaDB-1:10.11.6+maria~ubu2204-log created 231123 12:21:47 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
20NfZQ8BAAAA/AAAAAABAAABAAQAMTAuMTEuNi1NYXJpYURCLTE6MTAuMTEuNittYXJpYX51YnUy
MjA0LWxvZwAAAAAAAADbQ19lEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgEjYYYh
'/*!*/;
# at 256
#231123 12:21:47 server id 1 end_log_pos 299 CRC32 0xdd5f27d6 Gtid list [0-1-5]
# at 299
#231123 12:21:47 server id 1 end_log_pos 344 CRC32 0xca6cbf75 Binlog checkpoint mariadb-bin.000002
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
- 创建表和插入新值时,此文件中将有新的更改。
1.4 检查副本
- 检查副本的半同步状态(我们只检查一个副本。对于第二个副本,应该也会发生同样的情况)
$ docker exec -it mariadb-replica-2 mariadb -uroot -psecret -e "select @@rpl_semi_sync_slave_enabled;"
+-------------------------------+
| @@rpl_semi_sync_slave_enabled |
+-------------------------------+
| 1 |
+-------------------------------+
- 检查复制的数据库
$ docker exec -it mariadb-replica-1 mariadb -uroot -psecret -e "show databases like '%test%'";
+-------------------+
| Database (%test%) |
+-------------------+
| testdb |
+-------------------+
$ docker exec -it mariadb-replica-2 mariadb -uroot -psecret -e "show databases like '%test%'";
+-------------------+
| Database (%test%) |
+-------------------+
| testdb |
+-------------------+
1.5 开始复制
- 我们将执行 2 个事务。
- 对于每个事务,我们将检查
- 主库状态查询(见下文状态),
- 使用
mariadb-binlog
客户端验证二进制日志(见下文二进制日志), - 在副本上验证的复制结果(见下文副本)。
- 事务 1:
create table t(t int)
- 状态(我们获取到新的文件名和二进制日志中的当前位置)
MariaDB [testdb]> show master status;
+--------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------+----------+--------------+------------------+
| mariadb-bin.000003 | 485 | | |
+--------------------+----------+--------------+------------------+
1 row in set (0.000 sec)
- 事务 1:
create table t(t int)
- 二进制日志
$ docker exec mariadb-primary mariadb-binlog /var/lib/mysql/mariadb-bin.000003
#231123 12:57:18 server id 1 end_log_pos 299 CRC32 0x0f7f30b3 Gtid list [0-1-5]
# at 299
#231123 12:57:18 server id 1 end_log_pos 344 CRC32 0x263247e6 Binlog checkpoint mariadb-bin.000003
# at 344
#231123 14:05:49 server id 1 end_log_pos 386 CRC32 0x9f9ba209 GTID 0-1-6 ddl
...
create table t(t int)
...
- 事务 1:
create table t(t int)
- 副本 – 我们看到表已被复制,并且 IO/SQL 线程正在运行(来自
show replica hosts
的结果)。
- 副本 – 我们看到表已被复制,并且 IO/SQL 线程正在运行(来自
$ docker exec -it mariadb-replica-2 mariadb -uroot -psecret -e "use testdb; show tables; show create table t;"
+------------------+
| Tables_in_testdb |
+------------------+
| t |
+------------------+
+-------+------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------+
| t | CREATE TABLE `t` (
`t` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci |
+-------+------------------------------------------------------------------------------------------------------------------+
MariaDB [(none)]> show replica status \G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: mariadb-primary
Master_User: repl
Master_Port: 3306
Connect_Retry: 10
Master_Log_File: mariadb-bin.000003
Read_Master_Log_Pos: 485
Relay_Log_File: mariadb-relay-bin.000002
Relay_Log_Pos: 786
Relay_Master_Log_File: mariadb-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Rewrite_DB:
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 485
Relay_Log_Space: 1097
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_SSL_Crl:
Master_SSL_Crlpath:
Using_Gtid: Slave_Pos
Gtid_IO_Pos: 0-1-6
Replicate_Do_Domain_Ids:
Replicate_Ignore_Domain_Ids:
Parallel_Mode: optimistic
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Slave_DDL_Groups: 1
Slave_Non_Transactional_Groups: 0
Slave_Transactional_Groups: 0
1 row in set (0.000 sec)
- 事务 2:
insert into t values (1),(2),(314),(1618);
- 状态
MariaDB [testdb]> show master status;
+--------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+--------------------+----------+--------------+------------------+
| mariadb-bin.000003 | 668 | | |
+--------------------+----------+--------------+------------------+
1 row in set (0.000 sec)
- 事务 2:
insert into t values (1),(2),(314),(1618);
- 二进制日志(我们看到新事务的 GTID 数据流(
- 二进制日志(我们看到新事务的 GTID 数据流(
# at 485
#231123 14:46:19 server id 1 end_log_pos 527 CRC32 0x9ef73274 GTID 0-1-7 trans
START TRANSACTION
..
insert into t values (1),(2),(314),(1618)
...
- 事务 2:
insert into t values (1),(2),(314),(1618);
- 副本(检查复制的数据)
$ docker exec -it mariadb-replica-2 mariadb -uroot -psecret -e "select * from testdb.t"
+------+
| t |
+------+
| 1 |
| 2 |
| 314 |
| 1618 |
+------+
# Replica status changes
MariaDB [(none)]> show replica status \G
Slave_IO_State: Waiting for master to send event
Master_Log_File: mariadb-bin.000003
Read_Master_Log_Pos: 668
Relay_Log_File: mariadb-relay-bin.000002
Relay_Log_Pos: 969
Exec_Master_Log_Pos: 668
Relay_Log_Space: 1280
Using_Gtid: Slave_Pos
Gtid_IO_Pos: 0-1-7
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
2. 标准复制配置
- 在这里,我们将可视化标准复制如何配置,并稍后与半同步配置进行比较。
- 配置在上一篇博客中实现的标准复制非常直接

3. 标准复制事务
- 在这里,我们将可视化标准复制事务的流程,并稍后与半同步事务进行比较。
- 在线程级别(参见 复制线程),我们可以将活动事务的流程表达如下
- 客户端发起一个事务
- IO 线程通知 binlog-dump 线程它们已准备好接收事件
- Binlog-dump 线程转储事件
- IO 线程写入中继日志并更新 master.info 文件
- SQL 线程从中继日志读取并执行事件。

- 这种复制类型是异步的,这意味着我们无法从副本获得关于事件已被副本成功接收的反馈,如图所示。
4. 半同步复制配置
在确认客户端请求之前,至少一个副本只需确认已接收到数据更改(IO thread
),而不是副本实际上已应用了这些数据更改。要配置半同步复制,我们需要停止副本并在主库和副本上设置环境变量。
在主库上,设置 rpl_semi_sync_master_enabled
;在副本上,设置 rpl_semi_sync_slave_enabled
。如果设置正确,则相应的服务器上的 rpl_semi_sync_master_status
和 rpl_semi_sync_slave_status
应为开启状态。
我们将仅展示与第 2 节相比更新的配置。

5. 半同步复制事务示例
半同步通过引入一个额外的主线程(称为 “ACK 接收线程”)来克服这个问题。只需要一个副本确认它已接收并记录了事件,如下图所示。
流程如下
- 客户端发起一个事务,连接暂停直到收到 ACK [半同步]
- Binlog-dump 线程设置通知给 ACK 线程,以接收来自相关副本的 ACK [半同步]
- IO 线程通知 binlog-dump 线程它们已准备好接收事件
- Binlog-dump 线程转储事件
- IO 线程写入中继日志并更新 master.info 文件
- IO 线程通知 ACK 线程事件已接收。如果在配置的超时时间内没有 ACK,半同步将切换到异步 [半同步]
- ACK 线程解除客户端连接的暂停。客户端已准备好接收新事务 [半同步]
- SQL 线程从中继日志读取并执行事件。

结论与进一步阅读
特别感谢 Brandon Nesterenko、Daniel Black 和 Ian Gilfillan 审阅此博客文章。要获取有关副本状态的更多信息,可以使用 show replica hosts
(我们正在准备 MDEV-21322 ,以便为半同步状态提供更多详细信息)。
如果您在本博客、设计或不按预期工作的边缘情况中遇到任何问题,请告知我们。欢迎您在Zulip上讨论。一如既往,您可以在 MDEV 项目中使用我们的JIRA提交任何您遇到的 bug 或功能请求。
您好,感谢您提供详细信息。
我按照说明操作,收到了下面的错误;然而,当我尝试 isql 命令时,它奏效了。有什么建议吗?
ERROR 1105 (HY000): SQLDriverConnect: [unixODBC][Driver Manager]Can’t open lib ‘/usr/lib/oracle/12.1/client64/lib/libsqora.so.12.1’ : file not found