介绍
MySQL 复制可靠地将数据和操作从一个数据库镜像到另一个数据库。 传统复制涉及配置为接受数据库写入操作的主服务器,辅助服务器将操作从主服务器的日志复制并应用到它们自己的数据集。 这些辅助服务器可用于读取,但通常无法执行数据写入。
组复制是一种实现更灵活、容错的复制机制的方法。 此过程涉及建立一个服务器池,每个服务器都参与确保正确复制数据。 如果主服务器出现问题,成员选举可以从组中选择一个新的主服务器。 这允许其余节点继续运行,即使在遇到问题时也是如此。 成员协商、故障检测和消息传递是通过 Paxos 共识算法 的实现提供的。
在本教程中,我们将使用一组三个 Ubuntu 16.04 服务器设置 MySQL 组复制。 该配置将涵盖如何操作单个主或多主复制组。
先决条件
接下来,您将需要一组三台 Ubuntu 16.04 服务器。 在这些服务器中的每一个上,您都需要设置一个具有 sudo
权限的非 root 用户并配置一个基本的防火墙。 我们将使用 Ubuntu 16.04 的 初始服务器设置指南来满足这些要求并使每台服务器进入就绪状态。
Ubuntu 默认存储库中的 MySQL 版本不包括我们需要的组复制插件。 值得庆幸的是,MySQL 项目为包含该组件的最新 MySQL 版本维护了自己的存储库。 按照我们关于 在 Ubuntu 16.04 上安装最新 MySQL 的指南,在每台服务器上安装支持组复制的 MySQL 版本。
生成 UUID 以识别 MySQL 组
在打开 MySQL 配置文件来配置组复制设置之前,我们需要生成一个 UUID,我们可以使用它来识别我们将创建的 MySQL 组。
在 mysqlmember1 上,使用 uuidgen
命令为组生成有效的 UUID:
uuidgen
Output959cf631-538c-415d-8164-ca00181be227
复制您收到的值。 在为我们的服务器池配置组名时,我们将不得不参考这一点。
在 MySQL 配置文件中设置组复制
现在我们准备修改 MySQL 的配置文件。 在 每个 MySQL 服务器 上打开主 MySQL 配置文件:
sudo nano /etc/mysql/my.cnf
默认情况下,此文件仅用于从子目录中获取其他文件。 我们必须在 !includedir
行的 下方添加我们自己的配置 。 这使我们可以轻松地覆盖包含文件中的任何设置。
首先,通过包含 [mysqld]
标头为 MySQL 服务器组件打开一个部分。 在此之下,我们将粘贴组复制所需的设置。 loose-
前缀允许 MySQL 优雅地处理它不能优雅地识别而不会失败的选项。 稍后我们将需要填写和自定义其中的许多设置:
/etc/mysql/my.cnf
. . . !includedir /etc/mysql/conf.d/ !includedir /etc/mysql/mysql.conf.d/ [mysqld] # General replication settings gtid_mode = ON enforce_gtid_consistency = ON master_info_repository = TABLE relay_log_info_repository = TABLE binlog_checksum = NONE log_slave_updates = ON log_bin = binlog binlog_format = ROW transaction_write_set_extraction = XXHASH64 loose-group_replication_bootstrap_group = OFF loose-group_replication_start_on_boot = OFF loose-group_replication_ssl_mode = REQUIRED loose-group_replication_recovery_use_ssl = 1 # Shared replication group configuration loose-group_replication_group_name = "" loose-group_replication_ip_whitelist = "" loose-group_replication_group_seeds = "" # Single or Multi-primary mode? Uncomment these two lines # for multi-primary mode, where any host can accept writes #loose-group_replication_single_primary_mode = OFF #loose-group_replication_enforce_update_everywhere_checks = ON # Host specific replication configuration server_id = bind-address = "" report_host = "" loose-group_replication_local_address = ""
我们将上面的配置分为四个部分。 现在让我们来看看它们。
样板组复制设置
第一部分包含不需要修改的组复制所需的常规设置:
/etc/mysql/my.cnf
. . . # General replication settings gtid_mode = ON enforce_gtid_consistency = ON master_info_repository = TABLE relay_log_info_repository = TABLE binlog_checksum = NONE log_slave_updates = ON log_bin = binlog binlog_format = ROW transaction_write_set_extraction = XXHASH64 loose-group_replication_bootstrap_group = OFF loose-group_replication_start_on_boot = OFF loose-group_replication_ssl_mode = REQUIRED loose-group_replication_recovery_use_ssl = 1 . . .
这些设置打开全局事务 ID,配置组复制所需的二进制日志记录,并为组配置 SSL。 该配置还设置了一些其他有助于恢复和引导的项目。 您无需修改此部分中的任何内容,因此您可以在粘贴后继续。
共享组复制设置
第二部分为组设置共享设置。 我们必须自定义一次,然后在每个节点上使用相同的设置。 这包括组的 UUID、可接受成员的白名单以及要联系以从中获取初始数据的种子成员。
将 loose-group_replication_group_name
设置为您之前使用 uuidgen
命令生成的 UUID。 粘贴您复制的 UUID 作为此变量的值。
接下来,将 loose-group_replication_ip_whitelist
设置为所有 MySQL 服务器 IP 地址的列表,以逗号分隔。 loose-group_replication_group_seeds
设置应该与白名单几乎相同,但应该将我们将使用的组复制端口附加到每个成员的末尾。 对于本指南,我们将使用推荐的 33061 端口进行组复制:
/etc/mysql/my.cnf
. . . # Shared replication group configuration loose-group_replication_group_name = "959cf631-538c-415d-8164-ca00181be227" loose-group_replication_ip_whitelist = "203.0.113.1,203.0.113.2,203.0.113.3" loose-group_replication_group_seeds = ""203.0.113.1:33061,203.0.113.2:33061,203.0.113.3:33061" . . .
这部分在你的每台 MySQL 服务器上应该是相同的,所以一定要仔细复制它。
选择单主或多主
接下来,您需要决定是配置单主组还是多主组。 在 MySQL 官方文档的某些部分,这种区别也被称为“单”与“多主”复制。 在单主配置中,MySQL 指定一个主服务器(几乎总是第一个组成员)来处理写入操作。 多主组允许写入任何组成员。
如果您希望配置多主组,请取消注释 loose-group_replication_single_primary_mode
和 loose-group_replication_enforce_update_everywhere_checks
指令。 这将建立一个多主组。 对于单个主要组,只需将这两行注释掉:
/etc/mysql/my.cnf
. . . # Single or Multi-primary mode? Uncomment these two lines # for multi-primary mode, where any host can accept writes #loose-group_replication_single_primary_mode = OFF #loose-group_replication_enforce_update_everywhere_checks = ON . . .
这些设置在每个 MySQL 服务器上必须相同。
您可以稍后更改此设置,但必须重新启动 MySQL 组。 要切换到新配置,您必须停止组中的每个 MySQL 实例,使用新设置启动每个成员,然后重新引导组复制。 这不会影响您的任何数据,但需要一小段停机时间。
特定于主机的配置设置
第四部分包含每个服务器上不同的设置,包括:
- 服务器标识
- 要绑定的地址
- 向其他成员报告的地址
- 本地复制地址和监听端口
server_id
指令必须设置为唯一编号。 对于第一个成员,只需将其设置为“1”并增加每个附加主机上的数字。 将 bind-address
和 report_host
设置为当前服务器的 IP 地址,以便 MySQL 实例侦听外部连接并将其地址正确报告给其他主机。 loose-group_replication_local_address
也应设置为当前服务器的 IP 地址,组复制端口 (33061) 附加到 IP 地址:
/etc/mysql/my.cnf
. . . # Host specific replication configuration server_id = 1 bind-address = "203.0.113.1" report_host = "203.0.113.1" loose-group_replication_local_address = "203.0.113.1:33061"
在每个 MySQL 服务器上完成此过程。
完成后,请仔细检查每台主机上的共享复制设置是否相同,以及是否为每台主机定制了特定于主机的设置。 完成后保存并关闭每个主机上的文件。
重启 MySQL 并启用远程访问
我们的 MySQL 配置文件现在包含引导 MySQL 组复制所需的指令。 要将新设置应用于 MySQL 实例,请使用以下命令在 每个服务器 上重新启动服务:
sudo systemctl restart mysql
在 MySQL 配置文件中,我们将服务配置为在默认端口 3306 上侦听外部连接。 我们还将 33061 定义为成员应用于复制协调的端口。
我们需要在防火墙中打开对这两个端口的访问,我们可以通过键入以下内容来做到这一点:
sudo ufw allow 33061 sudo ufw allow 3306
打开 MySQL 端口后,我们可以创建一个复制用户并启用组复制插件。
配置复制用户并启用组复制插件
在 每个 MySQL 服务器 上,以管理用户身份登录到您的 MySQL 实例以启动交互式会话:
mysql -u root -p
系统将提示您输入 MySQL 管理密码。 之后,您将进入 MySQL 会话。 我们需要做的第一件事是创建一个复制用户。
每个服务器上都需要一个复制用户来建立组复制。 因为每台服务器都会有自己的复制用户,所以我们需要在创建过程中关闭二进制日志。 否则,一旦复制开始,该组将尝试将复制用户从主服务器传播到其他服务器,从而与已经存在的复制用户产生冲突。
我们将要求复制用户使用 SSL,授予他们在服务器上的复制权限,然后刷新权限以实施更改。 之后,我们将重新启用二进制日志记录以恢复正常操作。 创建复制用户时确保使用安全密码:
SET SQL_LOG_BIN=0; CREATE USER 'repl'@'%' IDENTIFIED BY 'password' REQUIRE SSL; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; SET SQL_LOG_BIN=1;
接下来,我们需要设置 group_replication_recovery
通道以使用我们的新复制用户和关联的密码。 然后,每个服务器将使用这些凭据对组进行身份验证。
CHANGE MASTER TO MASTER_USER='repl', MASTER_PASSWORD='password' FOR CHANNEL 'group_replication_recovery';
有了复制用户,我们就可以启用组复制插件来准备初始化组。 由于我们使用的是最新版本的 MySQL,我们可以通过键入以下命令启用该插件:
INSTALL PLUGIN group_replication SONAME 'group_replication.so';
通过键入以下内容验证插件是否处于活动状态:
SHOW PLUGINS;
Output+----------------------------+----------+--------------------+----------------------+---------+ | Name | Status | Type | Library | License | +----------------------------+----------+--------------------+----------------------+---------+ | | | | | | | . . . | . . . | . . . | . . . | . . . | | | | | | | | group_replication | ACTIVE | GROUP REPLICATION | group_replication.so | GPL | +----------------------------+----------+--------------------+----------------------+---------+ 45 rows in set (0.00 sec)
group_replication
行确认插件已加载并且当前处于活动状态。
启动组复制
现在每个 MySQL 服务器都配置了复制用户并启用了组复制插件,我们可以开始启动我们的组。
引导第一个节点
要启动组,请在组 的单个成员上 完成以下步骤。
组成员在最初加入组时依靠现有成员发送复制数据、最新成员列表和其他信息。 因此,我们需要使用稍微不同的过程来启动初始组成员,以便它知道不要从其种子列表中的其他成员那里获得此信息。
如果设置,group_replication_bootstrap_group
变量告诉成员它不应该期望从对等点接收信息,而是应该建立一个新组并选举自己为主要成员。 由于唯一合适的情况是没有现有组成员时,我们将在引导组后立即关闭此功能:
SET GLOBAL group_replication_bootstrap_group=ON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_group=OFF;
该组应以该服务器作为唯一成员启动。 我们可以通过检查 performance_schema
数据库中 replication_group_members
表中的条目来验证这一点:
SELECT * FROM performance_schema.replication_group_members;
您应该看到代表当前主机的单行:
Output+---------------------------+--------------------------------------+--------------+-------------+--------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ | group_replication_applier | 13324ab7-1b01-11e7-9dd1-22b78adaa992 | 203.0.113.1 | 3306 | ONLINE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ 1 row in set (0.00 sec)
MEMBER_STATE
的 ONLINE
值表示该节点在组内完全可操作。
接下来,创建一个测试数据库和表来测试我们的复制:
CREATE DATABASE playground; CREATE TABLE playground.equipment ( id INT NOT NULL AUTO_INCREMENT, type VARCHAR(50), quant INT, color VARCHAR(25), PRIMARY KEY(id)); INSERT INTO playground.equipment (type, quant, color) VALUES ("slide", 2, "blue");
检查内容以确保其输入正确:
SELECT * FROM playground.equipment;
Output+----+-------+-------+-------+ | id | type | quant | color | +----+-------+-------+-------+ | 1 | slide | 2 | blue | +----+-------+-------+-------+ 1 row in set (0.00 sec)
我们现在已经验证了该服务器是该组的成员并且它具有写入功能。 现在其他服务器可以加入该组。
启动剩余节点
接下来,在 第二台服务器 上,启动组复制。 由于我们已经有一个活跃的成员,我们不需要引导组并且可以加入它:
START GROUP_REPLICATION;
在 第三台服务器 上,以相同的方式启动组复制:
START GROUP_REPLICATION;
再次查看会员列表。 您现在应该看到三个服务器:
SELECT * FROM performance_schema.replication_group_members;
Output+---------------------------+--------------------------------------+--------------+-------------+--------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ | group_replication_applier | 13324ab7-1b01-11e7-9dd1-22b78adaa992 | 203.0.113.1 | 3306 | ONLINE | | group_replication_applier | 1ae4b211-1b01-11e7-9d89-ceb93e1d5494 | 203.0.113.2 | 3306 | ONLINE | | group_replication_applier | 157b597a-1b01-11e7-9d83-566a6de6dfef | 203.0.113.3 | 3306 | ONLINE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ 3 rows in set (0.01 sec)
所有成员的 MEMBER_STATE
值都应为 ONLINE
。 对于新组,如果任何节点被列为 RECOVERING
超过一两秒,通常表明发生错误或配置错误。 检查 /var/log/mysql/error.log
上的日志以获取有关问题的更多信息。
检查测试数据库信息是否已复制到新成员上:
SELECT * FROM playground.equipment;
Output+----+-------+-------+-------+ | id | type | quant | color | +----+-------+-------+-------+ | 1 | slide | 2 | blue | +----+-------+-------+-------+ 1 row in set (0.01 sec)
如果数据在新成员上可用,则意味着组复制工作正常。
测试新组成员的写入能力
接下来,我们可以尝试从我们的新成员写入数据库。 这是否成功取决于您选择配置单个主组还是多主组。
在单一主要环境中测试写入
在单个主组中,出于一致性原因,您应该期望来自非主服务器的任何写入操作都被拒绝。 您可以随时使用以下查询发现当前主节点:
SHOW STATUS LIKE '%primary%';
Output+----------------------------------+--------------------------------------+ | Variable_name | Value | +----------------------------------+--------------------------------------+ | group_replication_primary_member | 13324ab7-1b01-11e7-9dd1-22b78adaa992 | +----------------------------------+--------------------------------------+ 1 row in set (0.01 sec)
查询的值将是一个 MEMBER_ID
,您可以像我们之前那样通过查询组成员列表来匹配主机:
SELECT * FROM performance_schema.replication_group_members;
Output+---------------------------+--------------------------------------+--------------+-------------+--------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ | group_replication_applier | 13324ab7-1b01-11e7-9dd1-22b78adaa992 | 203.0.113.1 | 3306 | ONLINE | | group_replication_applier | 1ae4b211-1b01-11e7-9d89-ceb93e1d5494 | 203.0.113.2 | 3306 | ONLINE | | group_replication_applier | 157b597a-1b01-11e7-9d83-566a6de6dfef | 203.0.113.3 | 3306 | ONLINE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ 3 rows in set (0.01 sec)
在这个例子中,我们可以看到 203.0.113.1
的主机当前是主服务器。 如果我们尝试从另一个成员写入数据库,我们应该预期操作会失败:
INSERT INTO playground.equipment (type, quant, color) VALUES ("swing", 10, "yellow");
OutputERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement
这是意料之中的,因为该组当前配置有一个可写入的主数据库。 如果主服务器有问题离开组,组会自动选举一个新成员作为主服务器并接受写入。
在多主环境中测试写入
对于已配置为多主方向的组,任何成员都应该能够向数据库提交写入。
您可以通过再次检查 group_replication_primary_member
变量的值来仔细检查您的组是否在多主模式下运行:
SHOW STATUS LIKE '%primary%';
Output+----------------------------------+-------+ | Variable_name | Value | +----------------------------------+-------+ | group_replication_primary_member | | +----------------------------------+-------+ 1 row in set (0.02 sec)
如果变量为空,这意味着没有指定的主主机并且任何成员都应该能够接受写入。
通过键入以下内容在您的 第二台服务器 上进行测试:
INSERT INTO playground.equipment (type, quant, color) VALUES ("swing", 10, "yellow");
OutputQuery OK, 1 row affected (0.00 sec)
第二台服务器提交了写操作,没有任何错误。
在 第三台服务器 上,查询是否添加了新项目:
SELECT * FROM playground.equipment;
Output+----+-------+-------+--------+ | id | type | quant | color | +----+-------+-------+--------+ | 1 | slide | 2 | blue | | 2 | swing | 10 | yellow | +----+-------+-------+--------+ 2 rows in set (0.00 sec)
这确认第二台服务器的写入已成功复制。
现在,通过键入以下命令在第三台服务器上测试写入功能:
INSERT INTO playground.equipment (type, quant, color) VALUES ("seesaw", 3, "green");
OutputQuery OK, 1 row affected (0.02 sec)
回到 第一台服务器 ,测试以确保两个新成员的写操作都被复制回来:
SELECT * FROM playground.equipment;
Output+----+--------+-------+--------+ | id | type | quant | color | +----+--------+-------+--------+ | 1 | slide | 2 | blue | | 2 | swing | 10 | yellow | | 3 | seesaw | 3 | green | +----+--------+-------+--------+ 3 rows in set (0.01 sec)
这证实了复制在每个方向上都在工作,并且每个成员都能够执行写操作。
恢复组
一旦组被引导,单个成员可以加入和离开而不影响可用性,只要有足够的成员来选举主服务器。 但是,如果进行了某些配置更改(例如在单主环境和多主环境之间切换),或者该组的所有成员都离开了,您可能需要重新引导该组。 您执行此操作的方式与您最初执行的方式完全相同。
在您的 第一台服务器 上,设置 group_replciation_bootstrap_group
变量,然后开始初始化组:
SET GLOBAL GROUP_REPLICATION_BOOTSTRAP_GROUP=ON; START GROUP_REPLICATION; SET GLOBAL GROUP_REPLICATION_BOOTSTRAP_GROUP=OFF;
一旦第一个成员开始组,其他成员可以加入:
START GROUP_REPLICATION;
为其他成员遵循此过程:
START GROUP_REPLICATION;
该组现在应该在线,所有成员都可用:
SELECT * FROM performance_schema.replication_group_members;
Output+---------------------------+--------------------------------------+--------------+-------------+--------------+ | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ | group_replication_applier | 13324ab7-1b01-11e7-9dd1-22b78adaa992 | 203.0.113.1 | 3306 | ONLINE | | group_replication_applier | 1ae4b211-1b01-11e7-9d89-ceb93e1d5494 | 203.0.113.2 | 3306 | ONLINE | | group_replication_applier | 157b597a-1b01-11e7-9d83-566a6de6dfef | 203.0.113.3 | 3306 | ONLINE | +---------------------------+--------------------------------------+--------------+-------------+--------------+ 3 rows in set (0.01 sec)
此过程可用于在必要时重新启动组。
MySQL 启动时自动加入组
使用当前设置,如果成员服务器重新启动,它不会在启动时自动重新加入组。 如果您希望成员自动重新加入群组,您可以稍微修改配置文件。
当您希望成员在启动时自动加入时,我们将概述的设置很有帮助。 但是,您应该注意一些事项:
首先,此设置仅影响 MySQL 实例本身的启动时间。 如果成员因超时问题从组中删除,但 MySQL 实例仍然在线,则该成员不会自动重新加入。
其次,在首次引导组时启用此设置可能是有害的。 当没有要加入的现有组时,MySQL 进程将需要很长时间才能启动,因为它会尝试联系其他不存在的成员进行初始化。 只有经过长时间的超时,它才会放弃并正常启动。 之后,您将不得不使用上述过程来引导组。
考虑到上述注意事项,如果您希望配置节点在 MySQL 启动时自动加入组,请打开主 MySQL 配置文件:
sudo nano /etc/mysql/my.cnf
在里面,找到 loose-group_replication_start_on_boot
变量,并将其设置为“ON”:
/etc/mysql/my.cnf
[mysqld] . . . loose-group_replication_start_on_boot = ON . . .
完成后保存并关闭文件。 该成员应在其 MySQL 实例下次启动时自动尝试加入该组。
结论
在本教程中,我们介绍了如何在三个 Ubuntu 16.04 服务器之间配置 MySQL 组复制。 对于单个主设置,成员将在必要时自动选择具有写入能力的主。 对于多主组,任何成员都可以执行写入和更新。
组复制提供了灵活的复制拓扑,允许成员随意加入或离开,同时提供数据一致性和消息排序的保证。 MySQL 组复制的配置可能有点复杂,但它提供了传统复制无法提供的功能。