如何在本地使用二进制日志与容器进行Primary/Replica复制

101 阅读7分钟

使用容器进行MariaDB复制

在这篇博客中,我们将演示如何使用二进制日志将运行在Docker容器中的MariaDB数据库(我们称之为主数据库)复制到一个或多个运行在Docker容器中的MariaDB服务器(我们称之为副本),这种方法可以创建二进制日志文件和包含数据库所有变化记录的索引(包括数据和结构)。你可以在这里找到复制工作的概述,你可以在这里找到如何设置复制的方法。在这个GitHub脚本中可以找到一个例子。

配置和启动主容器

在启动主容器之前,我们首先要对它进行配置。

主容器的选项文件

在我的当前目录中,我将创建一个名为config-files 的目录,并在里面创建一个名为primarycnf 的目录,其中应该有一个文件primary-1.cnf

$ mkdir -p config-files/primarycnf
$ touch config-files/primarycnf/primary-1.cnf

将以下内容写进primary-1.cnf

[mariadb]
log-bin                         # enable binary logging
server_id=3000                  # used to uniquely identify the server
log-basename=my-mariadb         # used to be independent of hostname changes 
                                # (otherwise name is <datadir>/mysql-bin)
#binlog-format=MIXED            #default 

现在我们来看看实际的配置文件:为了使复制发生,首先需要激活二进制日志。这是由log-bin系统变量控制的,它将创建文件,并存储在数据目录路径(datadir)中。由于配置了log-basename服务器变量,二进制日志将被存储在名为my-mariadb-bin.<extension number> 的文件中,其中前缀-bin 被添加到log-basename值中,当服务器启动、日志被刷新或达到最大二进制日志大小(max_binlog_size)时,扩展号被创建(并递增)。此外,这个选项将创建一个二进制日志索引my-mariadb-bin.index

每台服务器在复制架构中必须有唯一的代表,我们使用server_id系统变量来实现。

如果你愿意,你可以改变二进制日志格式。默认的是混合类型,它提供了ROW和基于STATEMENT的最佳选择。安全的语句通过基于语句的复制,节省空间,而不安全的语句则通过基于ROW的复制,确保主站和副本的一致性。

主容器启动时的初始化脚本

我们可能会在容器启动时执行自定义的SQL语句,为此我们要创建一个名为primaryinit 的文件夹,其中有一个文件primaryinit.sql

$ mkdir primaryinit
$ touch primaryinit/primaryinit.sql

primaryinit.sql 的内容。

CREATE USER 'repluser'@'%' IDENTIFIED BY 'replsecret';
GRANT REPLICATION SLAVE ON *.* TO 'repluser'@'%';
CREATE DATABASE primary_db;

上述SQL语句将创建一个具有特定复制权限的自定义用户,我们的复制体将使用该权限来连接到主服务器。我们还将创建一个样本数据库primary_db ,作为一个测试数据库。

启动主容器

让我们写一条命令,启动运行主MariaDB实例的容器。

下面我们将使用几个卷。

  1. 配置。目录primarycnf ,上面创建的目录将被挂载到/etc/mysql/conf.d目录中(这个目录中的所有*.cnf 文件将被全局选项文件所包含。关于如何做到这一点,请看容器内的/etc/mysql/mariadb.cnf )。
  2. 初始数据。容器启动时运行的初始化脚本,通过将primaryinit 目录挂载到/docker-entrypoint-initdb.d 目录中来传递。
  3. 数据目录(可选):主服务器的数据目录将在主机上创建一个目录log-files-primary/ ,对应于容器内的/var/lib/mysql 。如果它不存在,它将在主机上的容器启动期间被创建。

此外,我们添加了-w 选项作为工作目录,以防你需要登录到容器中检查二进制日志,这就是我们稍后要做的。

作为环境变量,MARIADB_ROOT_PASSWORD是用来设置根用户的密码,以及MYSQL_INITDB_SKIP_TZINFO环境变量是用来跳过日志中的警告的。这一切都基于最新的mariadb镜像并在后台运行。

$ docker run -d --rm --name mariadb-primary \
-v $PWD/config-files/primarycnf:/etc/mysql/conf.d:z \
-v $PWD/primaryinit:/docker-entrypoint-initdb.d:z \
-v $PWD/log-files-primary:/var/lib/mysql \
-w /var/lib/mysql \
-e MARIADB_ROOT_PASSWORD=secret \
-e MYSQL_INITDB_SKIP_TZINFO=Y \
mariadb:latest

检查主日志(查看入口和副本已启用)。

$ docker logs mariadb-primary
...
022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Temporary server started.
2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Securing system users (equivalent to running mysql_secure_installation)

2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/primaryinit.sql
2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Stopping temporary server
...
2022-04-05 15:16:07 0 [Note] mariadbd: ready for connections.
Version: '10.7.3-MariaDB-1:10.7.3+maria~focal-log'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution

有2个创建的二进制日志和一个索引。

# Log into the data directory and check for binary logs with log-basename
$ docker exec -it mariadb-primary bash
root@e591a4955306:/var/lib/mysql# ls |grep my-mariadb
my-mariadb-bin.000001
my-mariadb-bin.000002
my-mariadb-bin.index

在容器上执行SQL语句,检查主站状态二进制日志

$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "select version()"
+-----------------------------------------+
| version()                               |
+-----------------------------------------+
| 10.7.3-MariaDB-1:10.7.3+maria~focal-log |
+-----------------------------------------+
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show databases like 'primary%'"
+---------------------+
| Database (primary%) |
+---------------------+
| primary_db          |
+---------------------+

# Check master status
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show master status"
+-----------------------+----------+--------------+------------------+
| File                  | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-----------------------+----------+--------------+------------------+
| my-mariadb-bin.000002 |      347 |              |                  |
+-----------------------+----------+--------------+------------------+

# Show binary logs
$ docker exec mariadb-primary mariadb -uroot -psecret -e "show binary logs\G;"
*************************** 1. row ***************************
 Log_name: my-mariadb-bin.000001
File_size: 824
*************************** 2. row ***************************
 Log_name: my-mariadb-bin.000002
File_size: 347

由于我们运行的是MariaDB 10.7,我们可以使用工具mariadb-binlog (以前的mysqlbinlog )来检查二进制日志(注意,不需要为二进制日志设置路径,因为容器的工作目录是数据目录)。你可以阅读更多关于mariadb-binlog选项列表

# To check the binary log use mariadb-binlog utility
$ docker exec -it mariadb-primary mariadb-binlog my-mariadb-bin.000001

# Specify exact position of the binlog
$ docker exec -it mariadb-primary mariadb-binlog --start-position=702 --stop-position=703 --base64-output=never -d primary_db my-mariadb-bin.000001

现在我们已经完成了主服务器的设置!

配置并启动副本

复制品的选项文件

就像我们对主服务器所做的那样,我们需要用选项文件来配置副服务器(ies)。在config-files 目录中,创建包含文件secondary-1.cnfsecondary-1 目录。

$ mkdir -p config-files/secondary-1
$ touch config-files/secondary-1/secondary-1.cnf

secondary-1.cnf 的内容。

[mariadb]
server_id=3001                  # used to uniquely identify the server
log-basename=my-mariadb         # used to be independent of hostname changes 
                                # (otherwise name is <datadir>/mysql-bin)
replicate_do_db=primary_db      # replicate only this DB
#binlog-format=MIXED            #default 

一切都与我们的主配置相同,除了我们使用不同的server_id,并且我们用replicate_do_db系统变量指向我们想要复制的特定数据库(这是一个可选的步骤--没有它所有的数据库都会被复制)。

复制容器启动的初始化脚本

在进行这一步之前,需要找出主容器的IP,因为我们使用的虚拟桥网络,容器只能通过它们的私有IP访问。人们需要创建一个用户定义的网络,以通过其主机名来引用一个容器。要找到这个IP,请运行以下命令

$ docker exec mariadb-primary cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.2	e591a4955306

注意获得的IP172.17.0.2

在这里,我们将创建一个脚本,它将是我们的复制容器的入口点。创建文件夹secondaryinit ,并在其中创建一个文件replinit.sql ,该文件将有复制命令改变主站到(复制命令的完整列表可以在这里找到)。注意,我们使用主容器的IP作为master_host选项。

$ mkdir -p secondaryinit/
$ touch secondaryinit/replinit.sql

replinit.sql 内容。

CHANGE MASTER TO
  MASTER_HOST='172.17.0.2',
  MASTER_USER='repluser',
  MASTER_PASSWORD='replsecret',
  MASTER_PORT=3306,
  MASTER_CONNECT_RETRY=10;

启动复制容器

为了启动复制/辅助容器,运行

$ docker run -d --rm --name mariadb-secondary-1 \
-v $PWD/config-files/secondary-1:/etc/mysql/conf.d:z \
-v $PWD/secondaryinit:/docker-entrypoint-initdb.d:z \
-v $PWD/log-files-secondary-1:/var/lib/mysql \
-w /var/lib/mysql \
-e MARIADB_ROOT_PASSWORD=secret \
-e MYSQL_INITDB_SKIP_TZINFO=Y \
mariadb:latest

检查卷,并与主容器的卷进行比较(类似,对吗?)观察次级容器上创建的日志

$ docker logs mariadb-secondary-1
...
2022-04-05 15:34:20+00:00 [Note] [Entrypoint]: Temporary server started.
2022-04-05 15:34:20+00:00 [Note] [Entrypoint]: Securing system users (equivalent to running mysql_secure_installation)
2022-04-05 15:34:21+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/replinit.sql
2022-04-05 15:34:21 5 [Note] Master connection name: ''  Master_info_file: 'master.info'  Relay_info_file: 'relay-log.info'
2022-04-05 15:34:21 5 [Note] 'CHANGE MASTER TO executed'. Previous state master_host='', master_port='3306', master_log_file='', master_log_pos='4'. New state master_host='172.17.0.2', master_port='3306', master_log_file='', master_log_pos='4'.
2022-04-05 15:34:21+00:00 [Note] [Entrypoint]: Stopping temporary server
...
2022-04-05 15:34:22 4 [Note] Slave I/O thread: Start asynchronous replication to master 'repluser@172.17.0.2:3306' in log '' at position 4
2022-04-05 15:34:22 5 [Note] Slave SQL thread initialized, starting replication in log 'FIRST' at position 0, relay log './my-mariadb-relay-bin.000001' position: 4
2022-04-05 15:34:22 0 [Note] mariadbd: ready for connections.
Version: '10.7.3-MariaDB-1:10.7.3+maria~focal'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution
2022-04-05 15:34:22 4 [Note] Slave I/O thread: connected to master 'repluser@172.17.0.2:3306',replication started in log 'FIRST' at position 4

因此,仅仅从日志中我们就可以了解到我们的复制状态。我们可以看到,副本与主服务器相连。我们也可以从主站的日志中验证这一点。

$ docker logs mariadb-primary
...
2022-04-05 15:34:22 7 [Note] Start binlog_dump to slave_server(3001), pos(, 4), using_gtid(0), gtid('')

为了确认复制确实在工作,让我们检查一下primary_db 数据库以及二进制日志是否存在

# Check the database
$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'show databases like "primary%"'
+---------------------+
| Database (primary%) |
+---------------------+
| primary_db          |
+---------------------+

人们也可以运行命令 [show slave status](https://mariadb.com/kb/en/show-replica-status/)并得到以下结果。

$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'show slave status\G'
*************************** 1. row ***************************
                Slave_IO_State: Waiting for master to send event
                   Master_Host: 172.17.0.2
                   Master_User: repluser
                   Master_Port: 3306
                 Connect_Retry: 10
               Master_Log_File: my-mariadb-bin.000002
           Read_Master_Log_Pos: 347
                Relay_Log_File: my-mariadb-relay-bin.000004
                 Relay_Log_Pos: 651
         Relay_Master_Log_File: my-mariadb-bin.000002
              Slave_IO_Running: Yes
             Slave_SQL_Running: Yes
               Replicate_Do_DB: primary_db
                          [...]
           Exec_Master_Log_Pos: 347
                          [...]
       Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
                          [...]

在这种情况下,我们在副本上没有二进制日志,但如果我们愿意,服务器可以同时是主服务器和副本。如果我们想在副本上有二进制日志,那么当前的副本可以作为其他副本的主副本,我们应该在中间的 "主"(secondary-1.cnf )的选项文件中设置log_bin选项。如果我们想让源主站的更新保存在副本的二进制日志中,我们应该为此配置log_slave_updates 系统选项。

测试主站/复制站

让我们在主站的数据库中插入一些数据(MariaDB基金会团队中的名字),并检查主站状态(注意我们的binlog位置是347)。

# Insert data
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e 'USE primary_db; INSERT INTO primary_db.primary_tbl values  ("Anna"), ("Andreia"), ("Kaj"), ("Monty"), ("Ian"), ("Vicentiu"), ("Daniel"), ("Faustin"),("Vlad"),("Anel");'

# Check status 
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show master status"
+-----------------------+----------+--------------+------------------+
| File                  | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-----------------------+----------+--------------+------------------+
| my-mariadb-bin.000002 |      817 |              |                  |
+-----------------------+----------+--------------+------------------+

检查副本上的数据

$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'select * from primary_db.primary_tbl;'
+----------+
| name     |
+----------+
| Anna     |
| Andreia  |
| Kaj      |
| Monty    |
| Ian      |
| Vicentiu |
| Daniel   |
| Faustin  |
| Vlad     |
| Anel     |
+----------+

你可以在GitHub上找到复制的例子

就这样了。如果你添加一个以上的副本,也是同样的效果。实际上,你可以把它作为家庭作业来试试 🙂(提示:你在GitHub上的脚本里有)。

结论和未来工作

这篇博客展示了如何在本地使用二进制日志与容器进行Primary/Replica复制。在后面的一些博客中,我们将展示如何使用全局事务ID(GTID)。

欢迎大家在Zulip上聊一聊。