介绍
MySQL Group Replication可以说从mysql半同步机制,做到了副本冗余之间的最终一致性,并且通过Paxos协议,保证多节点数据的一致性。 但目前跨机房网络环境,还没有达到很好的情况下。单机房场景下非常友好,但跨机房部署节点就有很多不可预计的因素。
在使用MGR场景中为了保证跨机房实现同步,初期会采用主从复制的原理进行复制(MySQL8.0.22异步复制源asynchronous_connection_failover)。但还是存在一些问题。
为此MySQL Server 8.0.27版本推出了MySQL InnoDB ClusterSet为InnoDB集群部署提供了灾难方案。通过将一个主mgr集群和一个或多个在其他位置(比如不同的数据中心)的副本连接起来。InnoDB ClusterSet使用专用的ClusterSet复制通道自动管理从主集群到副本集群的复制。如果主集群由于数据中心的丢失或到它的网络连接的丢失而变得不可用,可以使一个副本集群激活以恢复服务的可用性。复制通道还是属于异步复制,所以通常会有一定的复制延迟。
InnoDB ClusterSet优先考虑可用性而不是数据一致性,以便最大限度地提高容灾能力。
当主集群遇到问题时,部分或全部复制集群与主集群不完全一致。在这些场景中,如果触发紧急故障转移,任何未复制或分散的事务都有丢失的风险,并且只能手动恢复和协调。不能保证在发生紧急故障转移时保留数据。
MySQL InnoDB ClusterSet架构图:
从上述官方图中可以了解到,最少需要mysql shell组件介入才能实现集群复制。当然mysql router结合更完美,对前段基本无感知方式。
-
InnoDB ClusterSet将可用性置于一致性之上,以最大限度地提高灾难容忍度。正常的复制滞后或网络分区可能意味着在主群集出现问题时,部分或全部副本群集与主群集不完全一致。在这些情况下,如果触发紧急故障切换,任何未复制或不一致的事务都有丢失的风险,并且只能手动恢复和协调(如果可以访问的话)。无法保证在发生紧急故障转移时数据会得到保留。
-
InnoDB ClusterSet作为解决方案。此解决方案将对写入性能产生显著影响,因为稳定和低延迟的网络对于InnoDB集群成员服务器相互通信以达成事务共识非常重要。
-
InnoDB ClusterSet不会自动故障转移到副本集群。由于可能会丢失事务,而且数据一致性得不到保证,因此管理员必须做出并执行执行紧急故障切换的决定。如果原始主群集保持联机状态,则应在与它取得联系后立即将其关闭。
-
InnoDB ClusterSet只支持异步复制,不能使用半同步复制。
-
InnoDB ClusterSet仅支持主实例和副本InnoDB Cluster实例的单一主模式。不支持多主模式。
-
InnoDB ClusterSet部署只能包含一个读写主集群。所有复制副本群集都是只读的。不允许使用具有多个主群集的活动-活动设置,因为在群集出现故障时无法保证数据一致性。
-
InnoDB ClusterSet不支持使用运行MySQL Server 5.7的实例。包含MySQL 5.7实例的InnoDB集群不能作为InnoDB集群集部署的一部分。
使用要求
1.设置InnoDB ClusterSet部署需要MySQL Server 8.0.27或更高版本、MySQL Shell 8.0.27和MySQL Router 8.0.27及更高版本
2.InnoDB Cluster元数据版本必须为2.1.0或更高版本。当您在集群上执行任何操作(例如dba.getCluster()命令)时,如果集群的元数据需要更新,AdminAPI会发出警告。您可以通过在MySQL Shell 8.0.27或更高版本中发出dba.upgradeMetadata()命令,将元数据更新到适合InnoDB ClusterSet操作的版本
3.InnoDB集群可以处于单主模式或多主模式,但InnoDB ClusterSet不支持多主模式。群集必须处于单一主模式。可以在MySQL Shell中使用cluster.switchToSinglePrimaryMode()设置单主。
4.必须在集群中的所有成员服务器上使用相同的值设置group_replication_view_change_uid系统变量,以便为视图更改事件提供替代uuid。从MySQL 8.0.27中,使用dba.createCluster()命令创建的InnoDB集群将为所有成员服务器上的系统变量生成并设置一个值。在MySQL 8.0.27之前创建的InnoDB Cluster可能没有系统变量集,但InnoDB ClusterSet创建过程会对此进行检查,
5.Cluster.rescan()命令可用于在InnoDB集群中的所有成员服务器上生成和设置group_replication_view_change_uid的值。在MySQL Shell 8.0.29之前,该命令会在扫描集群时自动执行此操作,之后必须重新启动集群才能实现更改。启用updateViewChangeUuid选项,以便在扫描过程中自动生成和设置值。重新启动集群后,可以重试InnoDB ClusterSet的创建过程。
6.任何成员服务器上都不得有来自组外服务器的入站复制通道。允许使用由组复制自动创建的通道(Group_Replication_applier和Group_replification_recovery)。
7.需要知道集群的InnoDB集群服务器配置帐户用户名和密码。此帐户用于创建和配置InnoDB Cluster和InnoDB ClusterSet部署的成员服务器。每个成员服务器只有一个服务器配置帐户。必须在群集中的每个成员服务器上使用相同的用户帐户名和密码(clusterAdmin)
8.在创建InnoDB ClusterSet部署时,InnoDB集群必须在线且健康,并且必须可以使用MySQL Shell访问其主要成员服务器。
9.MySQL路由器实例。一个或多个MySQL路由器实例,用于将客户端应用程序流量路由到InnoDB ClusterSet部署中的适当集群。
升级
软件升级避免不了,因为mgr使用的不是单一组件,需要按照一定的要求升级InnoDB ClusterSet中的服务器实例。最好无业务访问下进行升级。
建议的顺序如下:
1.升级MySQL router。
2.升级MySQL Shell。
3.升级MySQL服务器:
4.升级后状态检查。
升级中常用命令行:
<ClusterSet>.status() #检查状态
<ClusterSet>.status({extended:1}) #检查更详细的信息
<Cluster>.rescan() #重新扫描节点
dba.configureInstance() #函数检查使实例能够用于InnoDB集群所需的所有设置
<ClusterSet>.listRouters() #显示当前router信息
安装部署
测试验证,最起码需要2套MGR集群的资源。下面环境已按照好一套mgr集群上创建clusterset。
操作整个流程如下:
clusterset部署
#1.获取原cluster信息
mysql-js> var cluster=dba.getCluster()
#2.创建clusterset
mysql-js> myclusterset = cluster.createClusterSet('testclusterset')
A new ClusterSet will be created based on the Cluster 'mgrCluster'.
* Validating Cluster 'mgrCluster' for ClusterSet compliance.
* Creating InnoDB ClusterSet 'testclusterset' on 'mgrCluster'...
* Updating metadata...
ClusterSet successfully created. Use ClusterSet.createReplicaCluster() to add Replica Clusters to it.
<ClusterSet:testclusterset>
#3.查看clusteset状态
mysql-js>var myclusterset = dba.getClusterSet()
mysql-js> myclusterset.status()
{
"clusters": {
"mgrCluster": {
"clusterRole": "PRIMARY",
"globalStatus": "OK",
"primary": "ss301:3380"
}
},
"domainName": "testclusterset",
"globalPrimaryInstance": "ss301:3380",
"primaryCluster": "mgrCluster",
"status": "HEALTHY",
"statusText": "All Clusters available."
#4.添加cluterset,第二个mgr集群的第一个mysql节点(第二套集群还不能创建)
mysql-js> myclusterset.createReplicaCluster("192.168.244.132:3380", "clustertwo", {recoveryProgress: 1, timeout: 10})
Setting up replica 'clustertwo' of cluster 'mgrCluster' at instance 'ss302:3380'.
A new InnoDB Cluster will be created on instance 'ss302:3380'.
。。。
Creating InnoDB Cluster 'clustertwo' on 'ss302:3380'..
Replica Cluster 'clustertwo' successfully created on ClusterSet 'testclusterset'.
<Cluster:clustertwo>
#5.查看创建的复制集群情况:
mysql-js> myclusterset.status()
{
"clusters": {
"clustertwo": {
"clusterRole": "REPLICA",
"clusterSetReplicationStatus": "OK",
"globalStatus": "OK"
},
"mgrCluster": {
"clusterRole": "PRIMARY",
"globalStatus": "OK",
"primary": "ss301:3380"
}
},
"domainName": "testclusterset",
"globalPrimaryInstance": "ss301:3380",
"primaryCluster": "mgrCluster",
"status": "HEALTHY",
"statusText": "All Clusters available."
6.到这里,可以到新添加的节点用主从命令查看:
从这里可以确认,还是创建了主从复制
mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: ss301
Source_User: mysql_innodb_cs_84
Source_Port: 3380
Connect_Retry: 3
Source_Log_File: mysql-bin.000004
Read_Source_Log_Pos: 61961
Relay_Log_File: relay-log-clusterset_replication.000002
Relay_Log_Pos: 6129
Relay_Source_Log_File: mysql-bin.000004
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
。。。
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name: clusterset_replication
其实可以看出就是主从复制。在结合MySQL8.0.22版本异步复制源asynchronous_connection_failover。从这里可以了解clusterset API其实是把异步复制源进行进一步实现。
mysql> select * from replication_asynchronous_connection_failover;
+------------------------+-------+------+-------------------+--------+--------------------------------------+
| CHANNEL_NAME | HOST | PORT | NETWORK_NAMESPACE | WEIGHT | MANAGED_NAME |
+------------------------+-------+------+-------------------+--------+--------------------------------------+
| clusterset_replication | ss301 | 3380 | | 80 | 8080336f-ff45-11ed-9f9e-00505635a9dc |
| clusterset_replication | ss301 | 3381 | | 60 | 8080336f-ff45-11ed-9f9e-00505635a9dc |
| clusterset_replication | ss301 | 3382 | | 60 | 8080336f-ff45-11ed-9f9e-00505635a9dc |
+------------------------+-------+------+-------------------+--------+--------------------------------------+
7.MGRclustertwo集群添加节点
mysql-js> var cluster = dba.getCluster()
mysql-js> cluster.status();
{
"clusterName": "clustertwo",
"clusterRole": "REPLICA",
"clusterSetReplicationStatus": "OK",
"defaultReplicaSet": {
"name": "default",
"primary": "ss302:3380",
"ssl": "DISABLED",
"status": "OK_NO_TOLERANCE",
"statusText": "Cluster is NOT tolerant to any failures.",
"topology": {
"ss302:3380": {
"address": "ss302:3380",
"memberRole": "PRIMARY",
"mode": "R/O",
"readReplicas": {},
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"role": "HA",
"status": "ONLINE",
"version": "8.0.33"
}
},
"topologyMode": "Single-Primary"
},
"domainName": "testclusterset",
"groupInformationSourceMember": "ss302:3380",
"metadataServer": "ss301:3380"
}
mysql-js>cluster.addInstance('mgradmin@192.168.244.132:3381',{recoveryMethod: 'clone'})
Validating instance configuration at 192.168.244.132:3381...
This instance reports its own address as ss302:3381
Instance configuration is suitable.
NOTE: Group Replication will communicate with other members using 'ss302:3381'. Use the localAddress option to override.
。。。
* Waiting for server restart... ready
* ss302:3381 has restarted, waiting for clone to finish...
** Stage RESTART: Completed
* Clone process has finished: 212.23 MB transferred in about 1 second (~212.23 MB/s)
State recovery already finished for 'ss302:3381'
The instance 'ss302:3381' was successfully added to the cluster.
8.MGR clustertwo集群第二个节点登录,查看状态:
存在主从复制信息,但Replica_IO_Running和Replica_SQL_Running都是NO
mysql> show replica status\G;
*************************** 1. row ***************************
Replica_IO_State:
Source_Host: ss301
Source_User: mysql_innodb_cs_84
Source_Port: 3380
Connect_Retry: 3
Source_Log_File: mysql-bin.000004
Read_Source_Log_Pos: 56236
Relay_Log_File: relay-log-clusterset_replication.000002
Relay_Log_Pos: 4
Relay_Source_Log_File: mysql-bin.000004
Replica_IO_Running: No
Replica_SQL_Running: No
。。。
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name: clusterset_replication
#9.查看整体集群+clusterSet情况:
2个mgr集群 mgrCluster和clustertwo状态和节点复制信息
mysql-js> myclusterset=dba.getClusterSet()
<ClusterSet:testclusterset>
mysql-js> myclusterset.status({extended:1})
{
"clusters": {
"clustertwo": {
"clusterRole": "REPLICA",
"clusterSetReplication": {
"applierStatus": "APPLIED_ALL",
"applierThreadState": "Waiting for an event from Coordinator",
"applierWorkerThreads": 4,
"receiver": "ss302:3380",
"receiverStatus": "ON",
"receiverThreadState": "Waiting for source to send event",
"replicationSsl": null,
"source": "ss301:3380"
},
"clusterSetReplicationStatus": "OK",
"globalStatus": "OK",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"ss302:3380": {
"address": "ss302:3380",
"memberRole": "PRIMARY",
"mode": "R/O",
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"status": "ONLINE",
"version": "8.0.33"
},
"ss302:3381": {
"address": "ss302:3381",
"memberRole": "SECONDARY",
"mode": "R/O",
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"status": "ONLINE",
"version": "8.0.33"
},
"ss302:3382": {
"address": "ss302:3382",
"memberRole": "SECONDARY",
"mode": "R/O",
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"status": "ONLINE",
"version": "8.0.33"
}
},
"transactionSet": "8080336f-ff45-11ed-9f9e-00505635a9dc:1-133:1000071,808037c5-ff45-11ed-9f9e-00505635a9dc:1-6,86a473cf-ff42-11ed-b58a-00505635a9dc:1-22,ce5f0588-ff64-11ed-b1b2-0050563557d1:1-7",
"transactionSetConsistencyStatus": "OK",
"transactionSetErrantGtidSet": "",
"transactionSetMissingGtidSet": ""
},
"mgrCluster": {
"clusterRole": "PRIMARY",
"globalStatus": "OK",
"primary": "ss301:3380",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"ss301:3380": {
"address": "ss301:3380",
"memberRole": "PRIMARY",
"mode": "R/W",
"status": "ONLINE",
"version": "8.0.33"
},
"ss301:3381": {
"address": "ss301:3381",
"memberRole": "SECONDARY",
"mode": "R/O",
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"status": "ONLINE",
"version": "8.0.33"
},
"ss301:3382": {
"address": "ss301:3382",
"memberRole": "SECONDARY",
"mode": "R/O",
"replicationLagFromImmediateSource": "",
"replicationLagFromOriginalSource": "",
"status": "ONLINE",
"version": "8.0.33"
}
},
"transactionSet": "8080336f-ff45-11ed-9f9e-00505635a9dc:1-133:1000071,808037c5-ff45-11ed-9f9e-00505635a9dc:1-6,86a473cf-ff42-11ed-b58a-00505635a9dc:1-22,ce5f0588-ff64-11ed-b1b2-0050563557d1:1-5"
}
},
"domainName": "testclusterset",
"globalPrimaryInstance": "ss301:3380",
"metadataServer": "ss301:3380",
"primaryCluster": "mgrCluster",
"status": "HEALTHY",
"statusText": "All Clusters available."
}
到此MGR Clusterset配置完成。
mysqlrouter配置
mysql router配置基础操作一样,额外提供了不同集群节点访问的API实现。
shell# ./mysqlrouter --bootstrap root@192.168.244.131:3380 --directory /opt/mysqlrouter/mgr --name='Rome1' --user=root
ClusterSet 'testclusterset' can be reached by connecting to:
## MySQL Classic protocol
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447
## MySQL X protocol
- Read/Write Connections: localhost:6448
- Read/Only Connections: localhost:6449
MySQL 192.168.244.131:3380 JS > myclusterset.listRouters()
{
"domainName": "testclusterset",
"routers": {
"ss301::Rome1": {
"hostname": "ss301",
"lastCheckIn": null,
"roPort": "6447",
"roXPort": "6449",
"rwPort": "6446",
"rwXPort": "6448",
"targetCluster": null,
"version": "8.0.33"
}
}
}
#mysql router切换访问第二个实例:
#获取mgr集群信息,配置mysqlrouter
mysql-js> myclusterset.setRoutingOption('ss301::Rome1', 'target_cluster', 'clustertwo')
Routing option 'target_cluster' successfully updated in router 'ss301::Rome1'.
#客户端链接验证
shell# mysql -umgradmin -p123456 -h192.168.244.131 -P6447 -e "select @@hostname,@@port;"
+------------+--------+
| @@hostname | @@port |
+------------+--------+
| ss302 | 3380 |
+------------+--------+
备注:ClusterSet中因为只有一个mysql实例支持写入(read_only和super_read_only是off),同时setRoutingOption一次只能设置一个路由选项。所以mysql router进行切换的时,有可能会导致。多个router之间访问不一致和写无法操作现象。
应急预案
因为ClusterSet跨机房部署,索引对于这方面应该有一定应急预案。这里提供一些处理建议。
-
在进行紧急故障切换之后,并且ClusterSet的各个部分之间存在事务集不同的风险,必须将集群与写流量或所有流量隔离开来。
-
如果集群上的事务集(GTID集)不一致,请首先解决此问题。如果复制集群的GTID集与InnoDB clusterSet中主集群上的GTID不一致,clusterSet.status()命令会发出警告。处于此状态的副本群集的全局状态为OK_NOT_CONSISTENT
-
如果无法修复集群,可以使用ClusterSet.removeCluster()命令将其从InnoDB ClusterSet中删除。已删除的InnoDB集群无法添加回InnoDB集群集部署中。如果要在部署中再次使用服务器实例,则需要使用设置新的集群。
-
当修复了一个集群或进行了所需的维护时,可以使用ClusterSet.request()命令将其重新加入InnoDB ClusterSet。此命令验证群集是否能够重新加入、更新和启动ClusterSet复制通道,并从群集中删除任何无效状态。
总结
InnoDB ClusterSet优先考虑可用性而不是数据一致性,以便最大限度地提高容灾能力,数据一致性得到保证。
因为基于主从复制,紧急故障转移会带来丢失事务的风险,并为InnoDB ClusterSet造成大脑分裂的情况。如果无法快速修复主群集以恢复可用性,建议保留一个集群,其他集群关闭,然后在可能的情况下进行修复。
clusterset实现方式还是两个集群之间的主从复制。虽然mysql shell介入,但整个过程还是算比较复杂。运维DBA需要一定的实操能力。