20.4 使用 InnoDB 集群

本节说明如何使用 InnoDB 集群以及如何处理常见的 Management 任务。

检索 InnoDB 集群

使用dba.createCluster()创建集群时,该操作将返回一个可以分配给变量的 Cluster 对象。您可以使用该对象与集群一起使用,例如添加实例或检查集群的状态。如果您想在以后再次获取群集,例如在重新启动 MySQL Shell 之后,请使用dba.getCluster(name, [options])函数。例如:

mysql-js> var cluster1 = dba.getCluster()

如果未指定群集* name *,则返回默认群集。如果 MySQL Shell 全局会话当前连接到的服务器实例的 InnoDB 集群元数据中存储了多个集群,请指定要检索的集群的name *。

检查 InnoDB 集群状态

群集对象提供了status()方法,使您可以检查群集的运行方式。在检查 InnoDB 集群的状态之前,需要通过连接到其任何实例来获取对 InnoDB 集群对象的引用。但是,如果要更改群集的配置,则必须连接到“ R/W”实例。发出status()会根据您连接到的服务器实例知道的集群视图来检索集群的状态,并输出状态报告。

Important

集群中实例的状态直接影响状态报告中提供的信息。与属于该集群的实例相比,离开集群的实例提供的集群视图有所不同。因此,请确保您连接到的实例的状态为ONLINE

有关 InnoDB 集群如何运行的信息,请使用集群的status()方法:

mysql-js> var cluster = dba.getCluster()
mysql-js> cluster.status()
{
    "clusterName": "testCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "localhost:3320",
	"ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "localhost:3310": {
                "address": "localhost:3310",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "localhost:3320": {
                "address": "localhost:3320",
                "mode": "R/W",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            },
            "localhost:3330": {
                "address": "localhost:3330",
                "mode": "R/O",
                "readReplicas": {},
                "role": "HA",
                "status": "ONLINE"
            }
        }
    }
}

cluster.status()输出的信息提供以下信息:

  • clusterName:在dba.createCluster()期间分配给此集群的名称。

  • defaultReplicaSet:属于 InnoDB 集群并包含数据集的服务器实例。

  • primary:仅在集群以单主模式运行时显示。显示当前主实例的地址。如果未显示此字段,则说明集群正在多主模式下运行。

  • ssl:集群是否使用安全连接。显示REQUIREDDISABLED的值,具体取决于createCluster()addInstance()期间memberSslMode选项的配置方式。此参数返回的值对应于实例上的group_replication_ssl_mode服务器变量的值。参见保护您的集群

  • status:集群中此元素的状态。对于整个群集,这描述了此群集提供的高可用性。状态为以下之一:

  • ONLINE:该实例处于联机状态,并且正在参与集群。

    • OFFLINE:实例已与其他实例失去连接。

    • RECOVERING:实例正在尝试通过与群集同步来检索它成为ONLINE成员之前需要的事务。

    • UNREACHABLE:实例与集群失去通信。

    • ERROR:实例在恢复阶段或应用事务时遇到错误。

Important

实例进入ERROR状态后,super_read_only选项将设置为ON。要保持ERROR状态,您必须使用super_read_only=OFF手动配置实例。

  • (MISSING):实例状态,该实例是已配置集群的一部分,但当前不可用。

Note

MISSING状态特定于 InnoDB 群集,不是组复制生成的状态。 MySQL Shell 使用此状态来 table 示已在元数据中注册但在实时群集视图中找不到的实例。

  • 拓扑:已添加到集群的实例。

  • 实例的主机名:实例的主机名,例如 localhost:3310.

  • 作用:此实例在集群中提供什么功能。当前仅 HA,以实现高可用性。

  • 模式:服务器是读写(“ R/W”)还是只读(“ R/O”)。该模式指示R/W(可读和可写)或R/O(只读)。在单主数据库模式下,只有一个标记为“ R/W”的实例可以执行更新数据库的事务,因此它是主数据库。如果该实例由于某种原因(例如意外停止)而无法访问,则其余的“ R/O”实例之一将自动取代其位置,并成为新的“ R/W”主实例。在多主数据库模式下,所有实例均标记为“ R/W”,并且没有单个选举的主数据库。

描述 InnoDB 集群的结构

要获取有关 InnoDB 集群本身结构的信息,请使用cluster.describe()函数:

mysql-js> cluster.describe();
{
    "clusterName": "test",
    "adminType": "local",
    "defaultReplicaSet": {
        "name": "default",
        "instances": [
            {
                "name": "localhost:3310",
                "host": "localhost:3310",
                "role": "HA"
            },
            {
                "name": "localhost:3320",
                "host": "localhost:3320",
                "role": "HA"
            },
            {
                "name": "localhost:3330",
                "host": "localhost:3330",
                "role": "HA"
            }
        ]
    }
}

该函数的输出显示了 InnoDB 集群的结构,包括其所有配置信息,等等。

超级只读和实例

每当组复制停止时,super_read_only变量就会设置为ON,以确保不对该实例进行写操作。当您尝试通过以下 AdminAPI 命令使用此类实例时,可以选择在该实例上设置super_read_only=OFF

  • dba.configureLocalInstance()

  • dba.createCluster()

  • dba.rebootClusterFromCompleteOutage()

  • dba.dropMetadataSchema()

当 AdminAPI 遇到具有super_read_only=ON的实例时,在交互模式下,您可以选择设置super_read_only=OFF。例如:

mysql-js> var myCluster = dba.createCluster('testCluster')
A new InnoDB cluster will be created on instance 'ic@ic-1:3306'.

The MySQL instance at 'ic@ic-1:3306' currently has the super_read_only
system variable set to protect it from inadvertent updates from applications.
You must first unset it to be able to perform any changes to this instance.
For more information see: https://dev.mysql.com/doc/refman/en/server-system-variables.html#sysvar_super_read_only.

Note: there are open sessions to 'ic@ic-1:3306'.
You may want to kill these sessions to prevent them from performing unexpected updates:

1 open session(s) of 'ic@ic-1:3306'.

Do you want to disable super_read_only and continue? [y|N]:

显示到实例的当前活动会话数。您必须确保没有应用程序可能会无意中写入实例。通过回答y,您可以确认 AdminAPI 可以写入实例。如果列出的实例有多个打开的会话,请谨慎操作,然后再允许 AdminAPI 设置super_read_only=OFF

要强制该功能在脚本中设置super_read_only=OFF,请将clearReadOnly选项设置为true。例如dba.configureInstance(instance, {clearReadOnly: true}).

Management 沙箱实例

沙盒实例运行后,可以随时使用以下方法更改其状态:

  • 要停止沙箱实例,请使用dba.stopSandboxInstance(instance)。与dba.killSandboxInstance(instance)不同,这会优雅地停止实例。

  • 要启动沙箱实例,请使用dba.startSandboxInstance(instance)

  • 要杀死沙箱实例,请使用dba.killSandboxInstance(instance)。这会在没有适当停止的情况下停止实例,并且在模拟意外停止时很有用。

  • 要删除沙箱实例,请使用dba.deleteSandboxInstance(instance)。这将完全从文件系统中删除沙箱实例。

从 InnoDB 集群中删除实例

您可以随时从集群中删除实例。可以使用removeInstance()方法完成此操作,如以下示例所示:

mysql-js> cluster.removeInstance('root@localhost:3310')

自定义 InnoDB 集群

创建群集并向其中添加实例时,AdminAPI 会自动配置诸如组名,本地地址和种子实例之类的值。建议将这些默认值用于大多数部署,但是高级用户可以通过将以下选项传递给dba.createCluster()cluster.addInstance()来覆盖这些默认值。

要自定义 InnoDB 集群创建的复制组的名称,请将groupName选项传递给dba.createCluster()命令。这将设置group_replication_group_name系统变量。该名称必须是有效的 UUID。

要自定义实例为其他实例提供的连接地址,请将localAddress选项传递给dba.createCluster()cluster.addInstance()命令。以host:port格式指定地址。这将在实例上设置group_replication_local_address系统变量。该地址必须可供群集中的所有实例访问,并且必须仅保留用于内部群集通信。换句话说,请勿使用该地址与实例进行通信。

要自定义实例加入集群时用作种子的实例,请将groupSeeds选项传递给dba.createCluster()cluster.addInstance()命令。当新实例加入群集时,将联系种子实例,并用于向新实例提供当前组成员的详细信息。新实例使用这些详细信息来联系组成员以获取数据。这些地址被指定为以逗号分隔的列 table,例如host1:port1host2:port2。这将配置group_replication_group_seeds系统变量。

有关更多信息,请参见由这些 AdminAPI 选项配置的系统变量的文档。

重新加入集群

如果某个实例离开集群,例如由于它失去了连接并且没有或无法自动重新加入集群,则可能有必要在以后的阶段将其重新加入集群。要将实例重新加入集群,请发出cluster.rejoinInstance()

在没有实例的情况下,实例的配置会保留下来,例如,当您尚未在实例上发布dba.configureLocalInstance() * locally *但已将其添加到集群中时,实例重启后不会自动重新加入集群。解决方案是发出cluster.rejoinInstance(),以便将该实例再次添加到群集中。然后连接到实例,在本地运行 MySQL Shell 并发出dba.configureLocalInstance()。这样可以确保将 InnoDB 集群配置保留在实例的选项文件中,以使其能够自动重新加入集群。

Tip

如果实例具有super_read_only=ON,则可能需要确认 AdminAPI 可以设置super_read_only=OFF。有关更多信息,请参见超级只读和实例

从仲裁丢失中恢复群集

如果一个或多个实例失败,则群集可能会失去其仲裁,这是在新的主数据库中进行投票的能力。在这种情况下,您可以使用方法cluster.forceQuorumUsingPartitionOf()重新构建仲裁,如下面的 MySQL Shell 示例所示:

// open session to a cluster

mysql-js> cluster = dba.getCluster("prodCluster")

  // The cluster lost its quorum and its status shows
  // "status": "NO_QUORUM"

mysql-js> cluster.forceQuorumUsingPartitionOf("localhost:3310")

  Restoring replicaset 'default' from loss of quorum, by using the partition composed of [localhost:3310]

  Please provide the password for 'root@localhost:3310': ******
  Restoring the InnoDB cluster ...

  The InnoDB cluster was successfully restored using the partition from the instance 'root@localhost:3310'.

  WARNING: To avoid a split-brain scenario, ensure that all other members of the replicaset
  are removed or joined back to the group that was restored.

从主要中断中重启集群

如果您的群集完全瘫痪,则可以使用dba.rebootClusterFromCompleteOutage()确保正确配置了群集。如果集群已完全停止,则必须启动实例,然后才能启动集群。例如,如果已重新启动运行沙箱群集的计算机,并且实例位于端口 3310、3320 和 3330,请发出:

mysql-js> dba.startSandboxInstance(3310)
mysql-js> dba.startSandboxInstance(3320)
mysql-js> dba.startSandboxInstance(3330)

这样可以确保沙箱实例正在运行。对于生产部署,您必须在 MySQL Shell 外部启动实例。实例启动后,连接到实例并运行 MySQL Shell。然后通过发出以下命令重新启动集群:

mysql-js> shell.connect('root@localhost:3310');
mysql-js> var cluster = dba.rebootClusterFromCompleteOutage();

Tip

如果实例具有super_read_only=ON,则可能需要确认 AdminAPI 可以设置super_read_only=OFF。有关更多信息,请参见超级只读和实例

这样可确保在完全中断后正确重新配置群集。它使用 MySQL Shell 连接到的实例作为新的种子实例,并基于该实例的现有元数据恢复群集。

如果此过程失败,并且群集元数据已严重损坏,则可能需要删除元数据并从头开始重新创建群集。您可以使用dba.dropMetadataSchema()删除集群元数据。

Warning

当无法还原群集时,只能将dba.dropMetadataSchema()方法用作最后的手段。无法撤消。

重新扫描群集

如果在不使用 AdminAPI 的情况下对实例的配置进行了更改,则需要重新扫描集群以更新 InnoDB 集群元数据。例如,如果您手动将新实例添加到“组复制”组,则不会基于对群集的更改来修改 InnoDB 群集元数据,因为未使用 MySQL Shell。在这种情况下,必须使用cluster.rescan()重新扫描群集以更新 InnoDB 群集元数据。

运行命令cluster.rescan()后,将识别出属于新发现实例的实例。系统会提示您根据需要将每个这些新发现的实例添加到群集中,或者可以选择忽略它们。

还报告了不再属于集群或不可用的实例。在这种情况下,系统会提示您删除该实例,或者您以后可以尝试使用诸如cluster.rejoin('ic@ic-4:3306')之类的命令将其重新添加到群集中。

检查实例状态

cluster.checkInstanceState()函数可用于验证实例上的现有数据不会阻止它加入集群。该过程通过验证实例的全局事务标识符(GTID)状态与群集已处理的 GTID 进行比较而起作用。有关 GTID 的更多信息,请参见第 16.1.3.1 节,“ GTID 格式和存储”。通过此检查,您可以确定是否可以将已处理事务的实例添加到群集中。

下面演示了如何在运行中的 MySQL Shell 中发布此代码:

mysql-js> cluster.checkInstanceState('ic@ic-4:3306')

此函数的输出可以是以下之一:

  • 好的,新的:实例尚未执行任何 GTID 事务,因此它不会与集群执行的 GTID 冲突

  • 可恢复,可以:实例已执行的 GTID 与集群种子实例的已执行 GTID 不冲突

  • 错误发散:实例已执行的 GTID 与集群种子实例的已执行 GTID 发生了偏差

  • 错误 lost_transactions:实例的已执行 GTID 数量比集群种子实例的已执行 GTID 数量更多

具有良好状态的实例可以添加到群集中,因为实例上的任何数据都与群集一致。换句话说,正在检查的实例尚未执行任何与集群执行的 GTID 冲突的事务,并且可以恢复到与其余集群实例相同的状态。

解散 InnoDB 丛集

要分解一个 InnoDB 集群,您需要连接到一个读写实例,例如单主集群中的主实例,并使用Cluster.dissolve()命令。这将删除与群集关联的所有元数据和配置,并在实例上禁用组复制。在实例之间复制的任何数据都不会被删除。无法撤消群集的溶解,因此必须传递force: true来确认要溶解群集。例如:要再次创建它,请使用dba.createCluster()

mysql-js> session
<ClassicSession:root@localhost:3310>
mysql-js> cluster.dissolve({force:true})
The cluster was successfully dissolved.
Replication was disabled but user data was left intact.

Note

发出cluster.dissolve()后,分配给Cluster对象的任何变量都不再有效。

保护群集

可以将服务器实例配置为使用安全连接。有关在 MySQL 上使用 SSL 的一般信息,请参见第 6.3 节“使用加密的连接”。本节说明如何配置群集以使用 SSL。另一种安全可能性是配置哪些服务器可以访问群集,请参见创建服务器白名单

Important

将群集配置为使用 SSL 后,必须将服务器添加到ipWhitelist

使用dba.createCluster()设置群集时,如果服务器实例提供 SSL 加密,则会在种子实例上自动启用它。将memberSslMode选项传递给dba.createCluster()方法以指定其他 SSL 模式。群集的 SSL 模式只能在创建时设置。 memberSslMode选项是配置要使用的 SSL 模式的字符串,默认为AUTO。允许的值为DISABLEDREQUIREDAUTO。这些模式定义为:

  • 设置createCluster({memberSslMode:'DISABLED'})可确保为群集中的种子实例禁用 SSL 加密。

  • 设置createCluster({memberSslMode:'REQUIRED'}),然后为集群中的种子实例启用 SSL 加密。如果无法启用,则会引发错误。

  • 设置为createCluster({memberSslMode:'AUTO'})(默认值),则如果服务器实例支持 SSL 加密,则会自动启用 SSL 加密;如果服务器不支持 SSL 加密,则将禁用 SSL 加密。

Note

使用商业版本的 MySQL 时,默认情况下启用 SSL,您可能需要为所有实例配置白名单。参见创建服务器白名单

发出cluster.addInstance()cluster.rejoinInstance()命令时,将根据为种子实例找到的设置启用或禁用实例上的 SSL 加密。为了获得更多控制,cluster.addInstance()cluster.rejoinInstance()命令接受memberSslMode选项。当实例加入时,这可用于测试群集的 SSL 设置。在这种情况下,命令的行为是:

  • 设置memberSslMode:'DISABLED'可确保为群集中的实例禁用 SSL 加密。

  • 设置memberSslMode:'REQUIRED'会强制为集群中的实例启用 SSL 加密。

  • 设置memberSslMode:'AUTO'(默认设置)后,将根据种子实例(集群的其他成员)使用的设置以及实例本身提供的可用 SSL 支持,自动启用或禁用 SSL 加密。

当使用createCluster()adoptFromGR选项来采用现有的组复制组时,在采用的群集上不会更改 SSL 设置:

  • memberSslMode不能与adoptFromGR一起使用。

  • 如果采用的群集的 SSL 设置与 MySQL Shell 支持的设置不同,换句话说,用于组复制恢复和组通信的 SSL,则不会修改这两个设置。这意味着您无法将新实例添加到群集,除非您手动更改所采用群集的设置。

MySQL Shell 始终为组复制恢复和组通信启用或禁用群集的 SSL,请参见第 17.5.2 节“组复制安全套接字层(SSL)支持”。当将新实例添加到群集时,如果种子实例的设置不同(例如,使用adoptFromGRdba.createCluster()的结果),则会执行验证并发出错误。必须为集群中的所有实例启用或禁用 SSL 加密。进行验证以确保在将新实例添加到群集时此不变式成立。

deploySandboxInstance()命令默认情况下尝试部署具有 SSL 加密支持的沙箱实例。如果不可能,则部署服务器实例时不支持 SSL。使用ignoreSslError选项设置为 false 可以确保沙盒实例部署有 SSL 支持,如果无法提供 SSL 支持,则会发出错误。当ignoreSslError为 true(默认值)时,如果无法提供 SSL 支持并且服务器实例在没有 SSL 支持的情况下部署,则在操作期间不会发出任何错误。

创建服务器白名单

使用群集的createCluster()addInstance()rejoinInstance()方法时,可以选择指定属于该群集的已批准服务器的列 table,称为白名单。通过以这种方式显式指定白名单,可以提高群集的安全性,因为只有白名单中的服务器才能连接到群集。默认情况下,如果未明确指定,则白名单将自动设置为服务器具有网络接口的专用网络地址。要配置白名单,请在使用该方法时使用ipWhitelist选项指定要添加的服务器。将服务器作为逗号分隔的列 table 传递,并用引号引起来。使用ipWhitelist选项可在实例上配置group_replication_ip_whitelist系统变量。例如:

mysql-js> cluster.addInstance("ic@ic-3:3306", {ipWhitelist: "203.0.113.0/24, 198.51.100.110"})

这会将实例配置为仅接受来自地址203.0.113.0/24198.51.100.110的服务器的连接。从 MySQL 5.7.21 开始,白名单还可以包含主机名,只有在其他服务器发出连接请求时才能解析这些主机名。

Warning

主机名本质上不如白名单中的 IP 地址安全。 MySQL 执行 FCrDNS 验证,该验证提供了良好的保护级别,但可能会受到某些类型的攻击的危害。仅在绝对必要时在白名单中指定主机名,并确保所有用于名称解析的组件(例如 DNS 服务器)都在您的控制下。您也可以使用 hosts 文件在本地实现名称解析,以避免使用外部组件。

使用 MySQL Shell 执行脚本

您可以使用脚本自动执行集群配置。例如:

shell> mysqlsh -f setup-innodb-cluster.js

Note

在脚本文件名之后指定的任何命令行选项都将传递给脚本,而不是传递给 MySQL Shell。您可以使用 JavaScript 中的os.argv数组或 Python 中的sys.argv数组访问这些选项。在这两种情况下,在数组中选择的第一个选项是脚本名称。

示例脚本文件的内容如下所示:

print('MySQL InnoDB cluster sandbox set up\n');
  print('==================================\n');
  print('Setting up a MySQL InnoDB cluster with 3 MySQL Server sandbox instances.\n');
  print('The instances will be installed in ~/mysql-sandboxes.\n');
  print('They will run on ports 3310, 3320 and 3330.\n\n');

  var dbPass = shell.prompt('Please enter a password for the MySQL root account: ', {type:"password"});

  try {
     print('\nDeploying the sandbox instances.');
     dba.deploySandboxInstance(3310, {password: dbPass});
     print('.');
     dba.deploySandboxInstance(3320, {password: dbPass});
     print('.');
     dba.deploySandboxInstance(3330, {password: dbPass});
     print('.\nSandbox instances deployed successfully.\n\n');

     print('Setting up InnoDB cluster...\n');
     shell.connect('root@localhost:3310', dbPass);

     var cluster = dba.createCluster("prodCluster");

     print('Adding instances to the cluster.');
     cluster.addInstance({user: "root", host: "localhost", port: 3320, password: dbPass});
     print('.');
     cluster.addInstance({user: "root", host: "localhost", port: 3330, password: dbPass});
     print('.\nInstances successfully added to the cluster.');

     print('\nInnoDB cluster deployed successfully.\n');
  } catch(e) {
     print('\nThe InnoDB cluster could not be created.\n\nError: ' +
     + e.message + '\n');
}