MySQL InnoDB Cluster – A Hands on Tutorial

传统上,基于MySQL配置高可用(HA)是一项具有挑战的任务,特别是对于在MySQL方面没有进阶知识的人。从理解相关概念和技术,到相关工具的使用,特别是执行和编辑一些命令和文件,有很多你需要去了解的事情,即使只是计划做一次测试部署(Group Replication的快速入门指南应该会对你有所启发)。所以很多人一直拖着不去设置HA,直到灾难性打击和宕机事件的出现。

Oracle的MySQL团队最近发布了一个解决方案的预览版本,这个解决方案的目标是:让用户很容易就能把多个MySQL实例集成在一起提供冗余,来支持MySQL数据库高可用的特性。

在本文中,我们会使用新的MySQL Shell来创建三个沙箱实例,使用它们创建一个三个节点的Group Replication,并且使用MySQL Router作为中间层来将多个MySQL实例隐藏在一个TCP端口后。Group Replication为InnoDB提供了同步复制和内置的failover机制,同时Router可以让应用程序始终连接到集群中合适的节点上,除了对MySQL的连接参数做些调整外,不需要修改应用程序的任何代码。

img

下载和安装依赖包

除MySQL 5.7.17之外,你还需要从labs.mysql.com下载MySQL InnoDB Cluster包,里面包括以下内容:

  • MySQL Router 2.1.1 labs
  • MySQL Shell 1.0.6 labs with X AdminAPI

压缩包必须要解压,然后通过特定平台的具体方法,为各个组件安装对应的包。

部署沙箱实例

第一步是部署一个MySQL Server实例,这样我们就可以安全地玩耍以及试验,而不会影响已有的MySQL数据库。

启动MySQL Shell(像平常一样,使用non-root OS用户):

$ mysqlsh

然后执行:

mysql-js> dba.deploySandboxInstance(3310)

deploySandboxInstance()的参数是MySQL Server需要监听的TCP端口,默认情况下,沙箱会被创建在名为$HOME/mysql-sandboxes/(or C:\Users\\mysql-sandboxes\ in Windows)的目录下面。

命令执行完后会得到提示,为当前部署的实例设置一个MySQL root用户的密码,后面需要使用这个密码连接实例。

注意:在一个集群中的所有沙箱需要使用同样的密码

再重复两次上面的命令,每次使用不同的端口。这样,就创建了一个最多可以容忍一个节点挂掉的InnoDB Cluster。

kojima@VirtualBox:~$ mysqlsh
Welcome to MySQL Shell 1.0.6-labs Development Preview

Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type '\help', '\h' or '\?' for help, type '\quit' or '\q' to exit.

Currently in JavaScript mode. Use \sql to switch to SQL mode and execute queries.
mysql-js> dba.deploySandboxInstance(3310)
A new MySQL sandbox instance will be created on this host in 
/home/kojima/mysql-sandboxes/3310

Please enter a MySQL root password for the new instance: 
Deploying new MySQL instance...

Instance localhost:3310 successfully deployed and started.
Use '\connect root@localhost:3310' to connect to the instance.

mysql-js> dba.deploySandboxInstance(3320)
A new MySQL sandbox instance will be created on this host in 
/home/kojima/mysql-sandboxes/3320

Please enter a MySQL root password for the new instance: 
Deploying new MySQL instance...

Instance localhost:3320 successfully deployed and started.
Use '\connect root@localhost:3320' to connect to the instance.

mysql-js> dba.deploySandboxInstance(3330)
A new MySQL sandbox instance will be created on this host in 
/home/kojima/mysql-sandboxes/3330

Please enter a MySQL root password for the new instance: 
Deploying new MySQL instance...

Instance localhost:3330 successfully deployed and started.
Use '\connect root@localhost:3330' to connect to the instance.

初始化InnoDB Cluster

下一步,将shell连接到刚创建的三个实例中的一个,这个实例也即成为我们的种子实例。所谓种子实例,就是具有数据库初始状态的实例,后续加入到集群中的实例会从种子实例中复制状态到本地。由于这次我们用的是新建的空沙箱实例做演示,所以刚创建的三个MySQL实例中任何一个都可以作为种子实例。

mysql-js> shell.connect("root@localhost:3310");
Creating a Session to 'root@localhost:3310'
Enter password: 
Classic Session successfully established. No default schema selected.

下一步,创建InnoDB cluster。

mysql-js> cluster = dba.createCluster('mycluster');
A new InnoDB cluster will be created on instance 'root@localhost:3310'.
...
Creating InnoDB cluster 'mycluster' on 'root@localhost:3310'...
Adding Seed Instance...

Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
...

createCluster()命令需要一个参数,用来指定InnoDB cluster的名称。这个命令还会做一些其他的事情:

  • 在正在使用的实例上面部署元数据模式(mysql.mysql_innodb_cluster_metadata)
  • 针对Group Replication来校验配置的正确性,有必要的话要求修改
  • 在新集群中将自身注册为种子实例
  • 创建一些必要的内置帐号,比如复制账号
  • 启动Group Replication

沙箱实例使用复制来工作是提前配置好的。如果使用的是一个已经存在的实例,那么实例的一些参数配置可能需要修改。createCluster() (和addInstance()) 会确保设置都是正确的。

如果服务器尚未配置为Group Replication,那可能需要修改一些重要配置,比如开启Binary log(binlog),确保binlog格式是正确的等等。为了尽可能小的影响正在运行的节点,可以通过checkLocalInstance()来检查一个服务器是不是配置正确,并通过configLocalInstance()来重新配置。另外,如果配置修改需要重启MySQL Server,那么需要选择在合适的时间来手动处理。

向InnoDB Cluster中加入节点

现在,需要向集群中加入复制节点。我们这里使用之前创建好的空沙箱节点。通常情况下,当我们向集群中加入一个复制节点时,它的状态会落后于其它在线节点成员,并且需要追上种子实例的当前状态。如果种子实例中的存量数据非常大,可能需要事先用一种快速的方法克隆或者拷贝这些数据。MySQL Shell工具很快会提供一种机制让用户可以轻松地完成这个步骤。在其它方面,Group Replication会自动同步数据(这被称作Recovery),重新执行种子实例的所有事务,只要这些事务记录在MySQL Binary 日志中。由于在这个例子中,种子实例只有很少甚至没有数据(即只有元数据模式和内部帐户),并且从一开始就记录了二进制日志,新加入的节点只需要追很少的数据。在种子节点上面执行的任何事务,都会在每一个新加入节点中重新执行。

向集群中加入两个空实例:

mysql-js> cluster.addInstance("root@localhost:3320");
A new instance will be added to the InnoDB cluster. Depending on the amount of
data on the cluster this might take from a few seconds to several hours.

Please provide the password for 'root@localhost:3320': 
Adding instance to the cluster ...

The instance 'root@localhost:3320' was successfully added to the cluster.

mysql-js> cluster.addInstance("root@localhost:3330");
A new instance will be added to the InnoDB cluster. Depending on the amount of
data on the cluster this might take from a few seconds to several hours.

Please provide the password for 'root@localhost:3330': 
Adding instance to the cluster ...

The instance 'root@localhost:3330' was successfully added to the cluster.

现在我们检查一下集群的当前状态:

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

这个命令访问InnoDB Cluster的当前状态并且输出一个简短的报告。每一个实例的状态不是ONLINE就是RECOVERING。RECOVERING状态的意思是当前实例正在从种子节点中接收更新,并且最终会转换为ONLINE状态。

另一个关于How to navigate the InnoDB Cluster的文章已经可以看到,他解释了如何解读不同状态以及如何去影响他们。

另一个需要注意的点是,有一个实例(PRIMARY)被标记为R/W (可读/可写),而其它两个被标记为R/O (只读)。只有被标记为R/W的实例才可以执行更新数据库的事务。如果PRIMARY实例由于某种原因连不上了(如一次系统Crash),余下两个实例中的一个会自动取代它的位置并切换为新的PRIMARY。

部署MySQL Router

为了能处理failover,应用程序需要了解InnoDB Cluster的拓扑结构,需要在任何时候都知道哪个实例是PRIMARY。虽然应用程序可以自己实现这些,但MySQL Router可以替你来做,只需要很少的工作量并且不需要应用程序修改代码。

推荐的部署方式是将MySQL Router和应用程序部署在同一台机器上。在演示中,我们将所有的实例以及脚本运行在同一台机器上,对MySQL Router也一样。在引导过程中,MySQL Router需要连接集群并且有权限访问performance_schema、mysql_innodb_cluster_metadat,同时会创建一个被限制的只读帐号供自己使用。

如果MySQL Router已经安装好了,我们需要做的就是使用元数据服务器来引导它。在shell中通过执行下面的命令行选项来调用mysqlrouter:

kojima@VirtualBox:~$ mysqlrouter --bootstrap localhost:3310 --directory myrouter
Please enter MySQL password for root:

Bootstrapping MySQL Router instance at ..../myrouter...
MySQL Router has now been configured for the InnoDB cluster 'cluster'.

The following connection information can be used to connect to the cluster.

Classic MySQL protocol connections to cluster 'cluster':
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447

X protocol connections to cluster 'cluster':
- Read/Write Connections: localhost:64460
- Read/Only Connections: localhost:64470

现在MySQL Router连接到了InnoDB cluster,取到元数据,配置完待使用。它将自己配置为从自包含的目录运行,这样就可以在不需要root权限条件下,在同一台机器上部署多个MySQL Router实例。MySQL Router生成的配置中创建了4个端口:一个是读写会话(它会将连接重定向到PRIMARY),一个是只读会话(它会以轮盘赌的方式,将连接重定向到其中一个SECONDARY实例上),一个是经典的MySQL连接,另一个连接为X protocol。

如果你的应用建立了非常多的数据库连接导致PRIMARY过载,并且这些连接上不需要执行更新操作,这种情况下,可以限制连接使用只读会话来减少PRIMARY的压力。

一旦引导并配置完成,就可以启动MySQL Router(或者设置一个服务,在系统启动时自动将它启动):

$ myrouter/start.sh

现在可以使用MySQL client连接MySQL Router的一个端口,可以看到客户端是如何透明地连接到InnoDB Cluster中的实例的。为了找到客户端实际连接的实例是哪个,我们简单的访问一下port状态变量:

kojima@VirtualBox:~$ mysqlsh --uri root@localhost:6446
mysql-js> \sql
Switching to SQL mode... Commands end with ;
mysql-sql> select @@port;
+--------+
| @@port |
+--------+
| 3310 |
+--------+
1 row in set (0.00 sec)

可以看到,我们连接的是读写端口(6446),因而可以判定Router将我们连接到了PRIMARY实例,这个实例监听的端口是3310。现在,你可以尝试连接只读端口(6447),观察一下可以获得什么信息。

` \sql`命令可以将MySQL Shell从默认的JavaScript方式切换到SQL方式,这样就可以直接执行SQL语句了。

检查InnoDB Cluster的状态

为了在之后能检查InnoDB Cluster的状态,可以获取一个连接好的集群对象引用,不论连接是通过Router或者是直连到一个实例上面。如果想要对集群做修改,连接的时候必须使用读写端口。

kojima@VirtualBox:~$ mysqlsh --uri root@localhost:6446
Creating a Session to 'root@localhost:6446'
Enter password: 
...
mysql-js> cluster=dba.getCluster()
<Cluster:mycluster>
mysql-js> cluster.status()
{
 "clusterName": "mycluster",
...

测试InnoDB Cluster

下一步,我们将要测试一下InnoDB Cluster,检验failover是否真的可以正常工作。我们将会把PRIMARY kill掉,然后观察是不是有其它节点自动取代它。为了这个目的我们使用了dba.killLocalInstance() 函数 (\js 模式下)。

mysql-js> dba.killLocalInstance(3310)
The MySQL sandbox instance on this host in /home/kojima/mysql-sandboxes/3310 will be killed
Killing MySQL instance...
Instance localhost:3310 successfully killed.

之后我们再检查一下(从测试会话到MySQL Router)现在连接的是哪一个实例。第一个SELECT失败了是因为我们之前对PRIMARY的连接已经断掉了,MySQL Shell 会自动的重连。我们再次执行命令即可:

mysql-js> \sql
Switching to SQL mode... Commands end with ;
mysql-sql> SELECT @@port;
ERROR: 2013 (HY000): Lost connection to MySQL server during query
The global session got disconnected.
Attempting to reconnect to 'root@localhost:6446'...
The global session was successfully reconnected.
mysql-sql> SELECT @@port;
+--------+
| @@port |
+--------+
| 3330 |
+--------+
1 row in set (0.00 sec)
mysql-sql>

这说明了InnoDB Cluster可以为我们提供自动的failover,MySQL Router已经自动地连接到了新的PRIMARY实例,从而我们有了高可用。

有了这个,你就可以开始使用MySQL InnoDB clusters,轻松体验高可用了。

如果你在预览版中发现问题,或者对特性有需求,请在 bugs.mysql.com上面编辑一个bug报告,如果你不确定该怎么归类,请使用MySQL Server: Document Store: X Shell。

发表评论