Orchestrator:MySQL 高可用复制拓扑管理与故障切换实战指南

一、引言

在 GTID 时代,MHA 显得有些落伍了,因此大家会优先选择了由 GitHub 团队开源的 MySQL 高可用组件 Orchestrator。

Orchestrator 是 MySQL 复制拓扑 HA,管理和可视化工具,目前 Booking、Github、Google/Vitess 等公司在使用。

  • 项目地址:https://github.com/openark/orchestrator

二、Orchestrator 的特点

  • 组件自身高可用:Orchestrator 节点之间通过 Raft 协议实现数据一致性即高可用性。
  • 拓扑发现:主动搜寻拓扑并进行映射,读取基本的 MySQL 信息,例如复制状态和配置。
  • 拓扑重组:可以通过 binlog file:position, GTID, Pseudo GTID, Binlog Servers 复制规则,对 MySQL 拓扑进行重组。
  • 故障恢复:根据从拓扑本身获得的信息,它可以识别各种故障情况。可以手动或自动进行故障恢复,故障恢复前后都有对应的 Hook,比较灵活。
  • 多种操作方式:提供了三种操作方式,API 接口调用、命令行操作、Web 页面调用。

其具有如下主要功能:

  1. 可以自动发现 MySQL 集群,通过 MySQL 节点信息获取相关 slave 的信息,进而获取整个集群拓扑结构并将信息存储在后端数据库中。
  2. 基于复制集管理规则,提供自由重构复制集拓扑结构的功能,如将某个 slave 节点移动到其他 master 下。
  3. 自动探测故障并采取相应恢复措施,实现 MySQL 服务高可用的目的,减少人工介入。
  4. 提供 Web UI,API 以及命令行等入口操作,方便用户监控和操作集群。

三、三大核心特性

Orchestrator 还有三个重要特性:

3.1 Discover(发现)

Orchestrator 主动搜寻 MySQL 拓扑并进行映射。它能读取基本的 MySQL 信息,例如复制状态和配置。即使遇到故障,也可以为 MySQL 环境的拓扑提供流畅的可视化效果,包括复制问题。

3.2 Refactoring(重构)

Orchestrator 了解复制规则。它知道 binlog 文件:位置,GTID,伪 GTID,Binlog 服务器。重构复制拓扑可以是将副本拖放到另一个主副本下的问题。移动副本是安全的:orchestrator 将拒绝非法的重构尝试。通过各种命令行选项可以实现细粒度的控制。

3.3 Recover(恢复)

Orchestrator 使用全面方法来检测主库故障和级联中间主库的故障。根据从拓扑本身获得的信息,它可以识别各种故障情况。可通过配置,orchestrator 可以选择执行自动恢复(或允许用户选择手动恢复的类型)。在内部实现中间主库的恢复。orchestrator 通过 Hooks 进行自定义脚本支持故障切换。

四、架构模式

针对复制提供故障切换,从库故障发现。基于 GTID + 增强半同步的高可用架构,生产环境建议使用多点非共享架构,开启 Raft 保证 Orchestrator 自身高可用。

同时需要注意的是,Orchestrator 的 server 端需要和对外提供服务的 MySQL 放在一起的,Orchestrator 的专属后端可以放到远程服务器上,这点在配置文件中也有体现。

4.1 单点模式

单个 Orchestrator 对应单个后端 MySQL。这是最简单的部署模式,适合测试或小规模环境。

4.2 后端单点

后端可以为单点写或者主从模式,多个 Orchestrator 共享一个写节点后端。

4.3 后端多点(推荐)

后端为多点写集群模式,Orchestrator 之间数据相互同步。这是生产环境推荐的部署模式,每个 Orchestrator 节点都有自己的后端 MySQL,通过 Raft 协议在节点之间同步数据。

五、Orchestrator 不支持的 MySQL 拓扑发现类型

  • 三个或以上的 master 节点
  • MySQL 5.6 的并行复制
  • 多源复制(即一个 slave 有多个 master)
  • Galera/XtraDB Cluster

六、Orchestrator 原理

6.1 Orchestrator 如何做故障检测

每个 orchestrator 节点会有多个线程连接至被监控的实例,并执行以下 SQL 获取相关 MySQL 实例的信息,查询间隔为 InstancePollSeconds 秒(此参数默认为 5)。

Orchestrator 会将获取到的元数据信息写入自身后端的 MySQL 数据库中。

使用所有 MySQL 节点来判断 master 和 intermediate master(作为从库后边又接有从库)是否故障,避免由于网络问题导致判断失误。

通常情况下通过以下两点来确定是否发生故障:

  1. 是否可以连接到主库
  2. 是否可以连接到所有从库,并且确认它们是否可以连接到它们的主库

6.2 给定一个节点如何自动发现整个集群拓扑

给定主库,如何发现从库:

  1. 如果 DiscoverByShowSlaveHosts 参数为 true,则会尝试先通过 SHOW SLAVE HOSTS 命令去发现从库。

    • 从库设置了正确的 report_hostSHOW SLAVE HOSTS 中的 host 字段显示正确的 IP,则直接通过 SHOW SLAVE HOSTS 发现从库。
    • 从库设置了错误的 report_hostSHOW SLAVE HOSTS 中的 host 字段显示错误的 IP,则 orchestrator 找不到从库。
  2. 如果 DiscoverByShowSlaveHosts 参数为 false,则会通过 processlist 去发现从库。具体 SQL 如下:

1
2
3
SELECT SUBSTRING_INDEX(host, ':', 1) AS slave_hostname
FROM information_schema.processlist
WHERE command IN ('Binlog Dump', 'Binlog Dump GTID');

给定从库,如何发现主库:

通过 SHOW SLAVE STATUS 命令去发现主库。

6.3 Orchestrator 如何判断 MySQL 主从延迟

默认情况下是使用 SHOW SLAVE STATUS \G 来查看主从延迟,但是在某些情况下,这种数据可能不太准确,如:链式复制。

可以通过 pt-heartbeat 工具进行检测,然后设置 orchestrator 相关参数进行查询 SQL,参数为 ReplicationLagQuery,示例:

1
2
3
{
  "ReplicationLagQuery": "select absolute_lag from meta.heartbeat_view"
}

七、Orchestrator 安装与使用

7.1 准备三台机器,并且装上 MySQL 作为元数据库

1
2
3
4
-- 创建元数据库和账号
CREATE DATABASE orchestrator;
CREATE USER 'orchestrator'@'127.0.0.1' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON orchestrator.* TO 'orchestrator'@'127.0.0.1';

7.2 安装包下载解压到安装目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 下载二进制包
wget https://github.com/openark/orchestrator/releases/download/v3.2.4/orchestrator-3.2.4-linux-amd64.tar.gz

# 创建安装目录(自行选择)
mkdir /home/orchestrator

# 解压包到安装目录
tar xf orchestrator-3.2.4-linux-amd64.tar.gz -C /home/orchestrator

# 拷贝 orchestrator 命令及静态资源到安装目录
cp /home/orchestrator/usr/local/orchestrator/orchestrator /home/orchestrator
cp -r /home/orchestrator/usr/local/orchestrator/resources /home/orchestrator

7.3 Orchestrator 配置文件

此处只列出了一些关键参数,其余参数调整可以参考:https://github.com/openark/orchestrator/blob/master/docs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "MySQLTopologyUser": "orch_client",
  "MySQLTopologyPassword": "123456",
  "MySQLOrchestratorHost": "127.0.0.1",
  "MySQLOrchestratorPort": 3306,
  "MySQLOrchestratorDatabase": "orchestrator",
  "MySQLOrchestratorUser": "orchestrator",
  "MySQLOrchestratorPassword": "123456",
  "MySQLHostnameResolveMethod": "@@report_host",
  "RaftEnabled": true,
  "RaftDataDir": "/home/orchestrator",
  "RaftBind": "10.xxxx",
  "RaftNodes": [
    "10.xxxx",
    "10.xxxx",
    "10.xxxx"
  ]
}

7.4 配置 systemctl 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# cat /etc/systemd/system/orchestrator.service
[Unit]
Description=orchestrator: MySQL replication management and visualization
Documentation=https://github.com/openark/orchestrator
After=syslog.target network.target mysqld.service mysql.service

[Service]
User=root
Group=root
Type=simple
WorkingDirectory=/home/orchestrator
ExecStart=/bin/sh -c '/home/orchestrator/orchestrator --config=/home/orchestrator/orchestrator.conf.json http >> /home/var/log/orchestrator.log 2>&1'
EnvironmentFile=-/etc/sysconfig/orchestrator
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

7.5 启动 Orchestrator

1
2
systemctl daemon-reload
systemctl start orchestrator.service

启动成功后可以通过 web 查看被监控集群状态,默认地址为 http://node:3000

7.6 在被监控的 MySQL 集群上创建探测账号

1
2
CREATE USER 'orch_client'@'%' IDENTIFIED BY 'XXXXXXXXXXXXXXXXXXXXXX';
GRANT RELOAD, PROCESS, SUPER, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'orch_client'@'%';

7.7 接入 Orchestrator

此处直接通过 web 页面进行集群拓扑发现,也可通过命令行、API 调用进行自动发现,详情见:https://github.com/openark/orchestrator

输入被监控 MySQL 集群的任意节点地址,接入完成后等待数秒后可以看到对应 MySQL 集群的拓扑图。

八、Orchestrator 配置文件详解——基础与探测参数

以下为 Orchestrator 完整配置文件的各参数详解:

8.1 基础运行与日志配置

1
2
3
4
5
{
  "Debug": true,                    // debug模式,输出详细信息
  "EnableSyslog": false,            // 是否输出到系统日志里
  "ListenAddress": ":3000"          // orchestrator的监听端口,web端口
}

8.2 被管理 MySQL 实例配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "MySQLTopologyUser": "orch_client",         // 后端被管理的mysql实例中的账号,所有实例都要有
  "MySQLTopologyPassword": "123456",          // 密码
  "MySQLTopologyCredentialsConfigFile": "",   // 验证的配置文件,账号密码可以直接写入文件,读取
  "MySQLTopologySSLPrivateKeyFile": "",       // ssl验证文件
  "MySQLTopologySSLCertFile": "",
  "MySQLTopologySSLCAFile": "",
  "MySQLTopologySSLSkipVerify": true,         // 跳过验证
  "MySQLTopologyUseMutualTLS": false          // 使用TLS验证
}

8.3 Orchestrator 元数据库配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "MySQLOrchestratorHost": "127.0.0.1",              // orchestrator的IP,也可以是本机IP
  "MySQLOrchestratorPort": 3306,                     // orchestrator所在的端口
  "MySQLOrchestratorDatabase": "orchestrator",       // orchestrator元数据的数据库名称
  "MySQLOrchestratorUser": "root",                   // 管理orchestrator数据库的账户
  "MySQLOrchestratorPassword": "123456",             // 密码
  "MySQLOrchestratorCredentialsConfigFile": "",
  "MySQLOrchestratorSSLPrivateKeyFile": "",
  "MySQLOrchestratorSSLCertFile": "",
  "MySQLOrchestratorSSLCAFile": "",
  "MySQLOrchestratorSSLSkipVerify": true,
  "MySQLOrchestratorUseMutualTLS": false
}

8.4 探测与发现参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "MySQLConnectTimeoutSeconds": 1,               // orchestrator连接mysql超时秒数
  "DefaultInstancePort": 3307,                   // mysql实例的端口,对外提供服务的实例
  "DiscoverByShowSlaveHosts": true,              // 是否启用审查和自动发现
  "InstancePollSeconds": 5,                      // orchestrator探测mysql间隔秒数
  "SkipMaxScaleCheck": true,                     // 没有MaxScale binlogserver设置为true
  "UnseenInstanceForgetHours": 240,              // 忘记看不见的实例的小时数
  "SnapshotTopologiesIntervalHours": 0,          // 快照拓扑调用之间的小时间隔。默认值:0(禁用)
  "InstanceBulkOperationsWaitTimeoutSeconds": 10 // 执行批量(多个实例)操作时在单个实例上等待的时间
}

8.5 主机名解析配置

1
2
3
4
5
6
7
{
  "HostnameResolveMethod": "none",                   // 解析主机名,默认default 不解析为none
  "MySQLHostnameResolveMethod": "@@hostname",        // MySQL主机名解析
  "SkipBinlogServerUnresolveCheck": true,            // 跳过二进制服务器检测
  "ExpiryHostnameResolvesMinutes": 60,               // 域名检测过期分钟数
  "RejectHostnameResolvePattern": ""                 // 禁止的域名正则表达式
}

8.6 复制延迟与维护参数

1
2
3
4
5
6
7
8
9
{
  "ReasonableReplicationLagSeconds": 10,                  // 复制延迟高于10秒表示异常
  "ProblemIgnoreHostnameFilters": [],                     // 主机正则匹配筛选最小化
  "VerifyReplicationFilters": false,                      // 重构前检查复制筛选器
  "ReasonableMaintenanceReplicationLagSeconds": 20,       // 上移和下移的阈值
  "CandidateInstanceExpireMinutes": 60,                   // 实例过期分钟数
  "AuditLogFile": "",                                     // 审计日志
  "AuditToSyslog": false                                  // 审计日志输出到系统日志
}

8.7 Web UI 与认证配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "RemoveTextFromHostnameDisplay": ":3306",    // 去除集群的文本
  "ReadOnly": true,                            // 全局只读
  "AuthenticationMethod": "",                  // 身份验证模式
  "HTTPAuthUser": "",                          // http验证用户名
  "HTTPAuthPassword": "",                      // http验证密码
  "AuthUserHeader": "",                        // 指示身份验证用户的HTTP标头,当AuthenticationMethod为"proxy"时
  "PowerAuthUsers": [                          // 在AuthenticationMethod=="proxy"上,可以进行更改的用户列表。所有其他都是只读的
    "*"
  ],
  "ClusterNameToAlias": {                      // 正则表达式匹配群集名称与人类友好别名之间的映射
    "127.0.0.1": "test suite"
  }
}

8.8 集群检测查询配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "SlaveLagQuery": "",                          // 使用SHOW SLAVE STATUS进行延迟判断
  "DetectClusterAliasQuery": "SELECT SUBSTRING_INDEX(@@hostname, '.', 1)",  // 查询集群别名
  "DetectClusterDomainQuery": "",               // 可选查询(在拓扑实例上执行),返回此集群主服务器的VIP/CNAME/别名/任何域名。查询将仅在集群主机上执行。如果提供,必须返回一行,一列
  "DetectInstanceAliasQuery": "",               // 可选查询(在拓扑实例上执行),返回实例的别名。如果提供,必须返回一行,一列
  "DetectPromotionRuleQuery": "",               // 可选查询(在拓扑实例上执行),返回实例的提升规则。如果提供,必须返回一行,一列
  "DataCenterPattern": "[.]([^.]+)[.][^.]+[.]mydomain[.]com",                // 从正则表达式中筛选数据中心名称
  "PhysicalEnvironmentPattern": "[.]([^.]+[.][^.]+)[.]mydomain[.]com",       // 返回实例的物理环境
  "PromotionIgnoreHostnameFilters": [],         // Orchestrator不会使用主机名匹配模式来提升副本(通过-c recovery;例如,避免使用dev专用计算机)
  "DetectSemiSyncEnforcedQuery": ""             // 查询以确定是否强制完全半同步写入
}

8.9 Agent 与 SSL 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "ServeAgentsHttp": false,           // 产生一个agent的http接口
  "AgentsServerPort": ":3001",        // 可选,对于raft设置,此节点将向其对等方通告的HTTP地址
  "AgentsUseSSL": false,              // 当"true" orchestrator将使用SSL侦听代理端口以及通过SSL连接到代理时
  "AgentsUseMutualTLS": false,
  "AgentSSLSkipVerify": false,
  "AgentSSLPrivateKeyFile": "",
  "AgentSSLCertFile": "",
  "AgentSSLCAFile": "",
  "AgentSSLValidOUs": [],
  "UseSSL": false,                    // 在服务器Web端口上使用SSL
  "UseMutualTLS": false,
  "SSLSkipVerify": false,
  "SSLPrivateKeyFile": "",
  "SSLCertFile": "",
  "SSLCAFile": "",
  "SSLValidOUs": [],
  "URLPrefix": "",                    // 在非根Web路径上运行orchestrator的URL前缀,例如/orchestrator将其置于nginx之后
  "StatusEndpoint": "/api/status",    // 使用相互TLS时的有效组织单位
  "StatusSimpleHealth": true,
  "StatusOUVerify": false,
  "AgentPollMinutes": 60,             // 代理轮询之间的分钟数
  "UnseenAgentForgetHours": 6,        // 忘记看不见代理的小时数
  "StaleSeedFailMinutes": 60,         // 认为陈旧(无进展)种子失败的分钟数
  "SeedAcceptableBytesDiff": 8192     // 仍被视为成功复制的种子源和目标数据大小之间的字节数差异
}

8.10 Pseudo-GTID 配置

1
2
3
4
5
6
7
8
9
{
  "PseudoGTIDPattern": "",                          // 为空禁用伪GTID
  "PseudoGTIDPatternIsFixedSubstring": false,       // 如果为true,则PseudoGTIDPattern不被视为正则表达式而是固定子字符串,并且可以提高搜索时间
  "PseudoGTIDMonotonicHint": "asc:",                // Pseudo-GTID条目中的子字符串,表示Pseudo-GTID条目预计会单调递增
  "DetectPseudoGTIDQuery": "",                      // 可选查询,用于权威地决定是否在实例上启用伪gtid
  "BinlogEventsChunkSize": 10000,                   // SHOW BINLOG|RELAYLOG EVENTS LIMIT ?,X语句。较小意味着更少的锁定和工作要做
  "SkipBinlogEventsContaining": [],                 // 扫描/比较Pseudo-GTID的binlog时,跳过包含给定文本的条目。这些不是正则表达式(扫描binlog时会消耗太多的CPU),只需查找子字符串
  "ReduceReplicationAnalysisCount": true            // 当为true时,复制分析将仅报告可能首先处理问题的实例(例如,不报告大多数叶子节点)
}

8.11 Raft 高可用与后端配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "RaftEnabled": true,                  // raft模式
  "BackendDB": "mysql",                // 后台数据库类型
  "RaftBind": "192.168.89.103",        // 绑定地址,本机IP
  "RaftDataDir": "/var/lib/orchestrator",  // 数据目录,如果不存在,则自动创建
  "DefaultRaftPort": 10008,            // raft通信端口,所有机器必须保持一致
  "RaftNodes": [                       // raft节点,必须包含所有节点
    "192.168.89.100",
    "192.168.89.102",
    "192.168.89.103"
  ],
  "ConsulAddress": "",                 // 找到Consul HTTP api的地址。示例:127.0.0.1:8500
  "ConsulAclToken": ""                 // 用于写入Consul KV的ACL令牌
}

九、配置文件详解——故障恢复与 Hook 配置

9.1 故障恢复控制参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "FailureDetectionPeriodBlockMinutes": 60,       // 该时间内发现故障,不被多次发现
  "RecoveryPeriodBlockSeconds": 3600,             // 该时间内发现故障,不会多次转移
  "RecoveryIgnoreHostnameFilters": [],            // 恢复会忽略的主机
  "RecoverMasterClusterFilters": [                // 仅在匹配这些正则表达式模式的集群上进行主恢复(当然".*"模式匹配所有内容)
    "*"
  ],
  "RecoverIntermediateMasterClusterFilters": [    // 仅在与这些正则表达式模式匹配的集群上进行IM恢复
    "*"
  ]
}

9.2 Hook 执行流程配置

在故障探测、故障恢复等操作前后都可以自定义动作。可以并且应该使用以下占位符:{failureType}{failureDescription}{command}{failedHost}{failureCluster}{failureClusterAlias}{failureClusterDomain}{failedPort}{successorHost}{successorPort}{successorAlias}{countReplicas}{replicaHosts}{isDowntimed}{autoMasterRecovery}{autoIntermediateMasterRecovery}{isSuccessful}{lostReplicas}{countLostReplicas}

OnFailureDetectionProcesses:检测故障转移方案时执行的进程(在决定是否进行故障转移之前)

1
2
3
"OnFailureDetectionProcesses": [
  "echo 'Detected {failureType} on {failureCluster}. Affected replicas: {countSlaves} autoMasterRecovery: {autoMasterRecovery} losthost: {lostSlaves} slavehost: {slaveHosts} orchestratorHost: {orchestratorHost}' >> /tmp/recovery.log"
]

PreGracefulTakeoverProcesses:在执行故障转移之前执行的进程(中止操作应该是任何一次以非零代码退出;执行顺序未定义)

1
2
3
"PreGracefulTakeoverProcesses": [
  "echo 'Planned takeover about to take place on {failureCluster}. Master will switch to read_only autoMasterRecovery: {autoMasterRecovery} losthost: {lostSlaves} slavehost: {slaveHosts} orchestratorHost: {orchestratorHost}' >> /tmp/recovery.log"
]

PreFailoverProcesses:在执行故障转移之前执行的进程(中止操作应该是任何一次以非零代码退出;执行顺序未定义)

1
2
3
"PreFailoverProcesses": [
  "echo 'Will recover from {failureType} on {failureCluster}' >> /tmp/recovery.log"
]

PostFailoverProcesses:执行故障转移后执行的进程(执行顺序未定义)。额外可用占位符:{isSuccessful}{lostReplicas}{countLostReplicas}

1
2
3
"PostFailoverProcesses": [
  "echo '(for all types) Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Successor: {successorHost}:{successorPort}' >> /tmp/recovery.log"
]

PostUnsuccessfulFailoverProcesses:在未完全成功的故障转移后执行的进程

1
"PostUnsuccessfulFailoverProcesses": []

PostMasterFailoverProcesses:执行主故障转移后执行的进程(使用与 PostFailoverProcesses 相同的占位符)

1
2
3
"PostMasterFailoverProcesses": [
  "echo 'Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Promoted: {successorHost}:{successorPort}' >> /tmp/recovery.log"
]

PostIntermediateMasterFailoverProcesses:成功的中间主恢复时执行(使用与 PostFailoverProcesses 相同的占位符)

1
2
3
"PostIntermediateMasterFailoverProcesses": [
  "echo 'Recovered from {failureType} on {failureCluster}. Failed: {failedHost}:{failedPort}; Successor: {successorHost}:{successorPort}' >> /tmp/recovery.log"
]

PostGracefulTakeoverProcesses:在运行正常的主接管后执行的进程(旧主位于新主之后执行)

1
2
3
"PostGracefulTakeoverProcesses": [
  "echo 'Planned takeover complete' >> /tmp/recovery.log"
]

9.3 故障转移行为参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "CoMasterRecoveryMustPromoteOtherCoMaster": true,     // 当'false'时,任何东西都可以升级(候选人优先于其他人)。当'true'时,orchestrator将促进其他共同主人或否则失败
  "DetachLostSlavesAfterMasterFailover": true,          // 恢复可能丢失一些副本。DetachLostReplicasAfterMasterFailover的同义词
  "ApplyMySQLPromotionAfterMasterFailover": true,       // orchestrator应该自己应用MySQL主促销:设置read_only=0,分离复制等
  "PreventCrossDataCenterMasterFailover": false,        // 当为true(默认值:false)时,不允许跨DC主故障转移,orchestrator将尽其所能只在同一DC内进行故障转移,否则根本不进行故障转移
  "MasterFailoverDetachSlaveMasterHost": false,         // 确保新主不复制旧主的数据。MasterFailoverDetachReplicaMasterHost的同义词
  "MasterFailoverLostInstancesDowntimeMinutes": 0,      // 在主故障转移(包括失败的主副本和丢失的副本)之后丢失的任何服务器停机的分钟数。0表示禁用
  "PostponeSlaveRecoveryOnLagMinutes": 0,               // PostponeReplicaRecoveryOnLagMinutes的同义词
  "OSCIgnoreHostnameFilters": []                        // OSC副本推荐将忽略与给定模式匹配的副本主机名
}

9.4 监控与其他配置

1
2
3
4
5
{
  "GraphiteAddr": "",
  "GraphitePath": "",
  "GraphiteConvertHostnameDotsToUnderscores": true
}

十、Orchestrator 在实际场景的应用——通过 Hook 实现故障切换

通过不同的 hook 之间的协作,实现自动切换与手动切换逻辑。在故障探测、故障恢复等操作前后都可以自定义动作。

10.1 自动切换

自动切换由 Orchestrator 发起,当 Orchestrator 节点判断主库 Dead,会发起自动 failover 流程。

  1. OnFailureDetectionProcesses Hook 中获取此次故障的类型(自动/手动),对于自动切换的场景发出主库故障报警
  2. Orchestrator 开始对集群进行切换,主要动作为:选出新主,关闭新主 read_only,修改旧的 slave 主从关系指向新主
  3. Orchestrator 开始进行故障切换后的工作,通过 PostFailoverProcesses Hook 实现,主要动作为:修改 DNS 指向,或者 VIP 指向,发出切换完成通知
  4. DBA 对旧主故障处理完成后,手动将旧主加回到集群

10.2 手动切换

手动切换由人工通过 web 页面/API 调用/命令行发起,此时 Orchestrator 节点会跳过故障探测。

  1. OnFailureDetectionProcesses Hook 中获取此次故障的类型(自动/手动),对于手动切换的场景,跳过故障报警
  2. Orchestrator 开始对集群进行切换,主要动作为:将旧主设置为 read_only,将旧主、旧 slave 的主从关系改为指向新主,关闭新主的 read_only
  3. Orchestrator 开始进行手动切换后的工作,通过 PostFailoverProcesses Hook 实现,主要动作为:修改 DNS 指向或 VIP,发出切换完成通知;手动切换后会通过 PostGracefulTakeover Hook 实现接下来的操作:将原主的连接进行主动断开,防止连接到旧主导致业务写入出错

十一、故障切换后的 ACK 机制

orchestrator 通过引入阻塞周期来避免抖动(级联故障导致持续中断和资源消耗),在任何给定的集群 orchestrator 上,除非人工允许,否则不会在小于所述周期的间隔内启动自动恢复。

  • 阻塞周期由 RecoveryPeriodBlockSeconds 参数控制,它仅适用于同一集群上的恢复。
  • 被挂起的故障恢复一旦超过 RecoveryPeriodBlockSeconds 设置的时间或者恢复已被确认就会解除阻塞。
  • 可以通过 Web API/界面(请参阅审核/恢复页面)或通过命令行界面来确认恢复:
1
orchestrator-client -c ack-cluster-recoveries -alias somealias
  • 手动恢复(例如 orchestrator-client -c recoverorchestrator-client -c force-master-failover)会忽略阻塞期。

十二、通过 API 实现集群管理

  • 对于新创建的 MySQL 集群,会在原有流程里边加上接入 Orchestrator 的准备工作,MySQL 运行后会调用自动发现 API 接口,将 MySQL 集群接入 Orchestrator。
  • 对于需要重启、开关机的 MySQL 集群,调用 API 接口,将此 MySQL 集群设置为 downtime(不切换标记),防止在操作过程中 Orchestrator 对该集群做了自动切换。
  • 对于需要修改项目名、集群名的 MySQL,通过调用 API 接口,对 Orchestrator 元数据进行修改,调整 MySQL 集群名。
  • 对于需要删除的 MySQL 集群,通过调用 API 接口将此集群的信息从 Orchestrator 中 forget 掉。

十三、如何调整故障处理敏感度

可以从 InstancePollSecondsReasonableInstanceCheckSeconds 两个配置项调节 orchestrator 对故障的容忍程度。

13.1 InstancePollSeconds

InstancePollSeconds 控制 mysql 实例存活状态检查 discover 流程的间隔,如果将其时间增长,就能过滤一些在间隔时间内 mysql 抖动的异常。如调整为 30 秒,对于发生在检查的间隔前 0~20 秒间的 10 秒异常,就能忽略三分之二的 10 秒的抖动异常。

13.2 ReasonableInstanceCheckSeconds

而对于 mysql 实例可以正常访问但是检查查询慢的问题,调整 InstancePollSeconds 并不能过滤这些异常,需要调整 ReasonableInstanceCheckSeconds 参数。ReasonableInstanceCheckSeconds 用于指定 orchestrator 在一次 discover 检查时允许的最长查询时间,对于能建立连接但是查询慢的抖动 mysql 实例,调大这个参数就能容忍更长的检查查询慢抖动异常,让 orchestrator 忽略这些可以容忍的抖动异常情况。

13.3 MySQLDiscoveryReadTimeoutSeconds

MySQLDiscoveryReadTimeoutSeconds 参数为 discover 检查查询结果返回的超时时间,默认为 10 秒。理应 ReasonableInstanceCheckSeconds 应小于等于 MySQLDiscoveryReadTimeoutSeconds

13.4 总结

所以总结来说,调大 InstancePollSecondsReasonableInstanceCheckSeconds 参数能够降低 orchestrator 对于异常抖动的敏感程度,避免在 mysql 实例发生一些轻微性能抖动时被 orchestrator 进行了故障处理。

十四、Hook 脚本案例

14.1 OnFailureDetectionProcesses 处理

其主要进行故障的报警通知工作。如果有预期不执行故障处理的情形,可以在这个阶段对故障进行判断和分类,按需过滤不需要执行恢复的故障,直接返回错误即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
orchestrator 自动切换后的 hook 脚本
"""
import sys

def _prompt(cluster_alias: str, failure_type: str, failed_host: str):
    log.error(f'{cluster_alias} 发生故障,故障类型:{failure_type} ,故障节点: {failed_host}')

if __name__ == '__main__':
    log.debug(f'参数个数为:{len(sys.argv)}')
    log.debug(f'参数详情:{sys.argv}')
    _prompt(cluster_alias=sys.argv[1],
            failure_type=sys.argv[2], failed_host=sys.argv[3])

14.2 PreFailoverProcesses.py

脚本主要做切换前最后的检查,切换备库域名以及 kill 掉原主节点。这里似乎没有确认上面的 kill 操作的效果是否达到预期。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FileName: PreFailoverProcesses.py
Description:
"""
import sys
import json
from datetime import datetime, timedelta

def _run_command(command: str, ip: str, sudo: bool = False, command_timeout: int = None):
    try:
        return ssh.run(
            host=ip,
            command=command,
            sudo=sudo,
            command_timeout=command_timeout,
        )
    except Exception as err:
        log.error(f'主库 {ip} 执行命令 {command} 出错,错误信息:{err}')

def _process(old_ip: str, cluster_alias: str, failure_type: str):
    """
    Args:
        old_ip:
    Returns:
    """
    # 判断是否需要进行切换
    log.info(f'检查 failure_type 类型 {failure_type}')
    if failure_type not in ('DeadCoMaster', 'DeadMaster', 'DeadCoMasterAndSomeReplicas'):
        log.error(f'集群有节点发生异常,但不是 DeadCoMaster或DeadMaster或DeadCoMasterAndSomeReplicas类型,所以不做切换动作')
        sys.exit(1)

    log.info(f' kill旧主:{old_ip} 的mysqld进程')
    mysql_kill = 'kill -9 $(pgrep -x mysqld)'
    ssh.run_command(mysql_kill, old_ip, command_timeout = 5)

if __name__ == '__main__':
    log.debug(f' pre_graceful_takeover 参数个数为:{len(sys.argv)}')
    log.debug(f'pre_graceful_takeover 参数详情:{sys.argv}')
    _process(old_ip=sys.argv[1], cluster_alias=sys.argv[2], failure_type=sys.argv[3])

14.3 PostFailoverProcesses.py

代码主要是确认故障主节点被 kill 后,将主节点的域名指向新的主节点,完成切换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""
FileName: PostFailoverProcesses.py
Description:
orchestrator 自动切换后的 hook 脚本
"""
import sys
import uuid
from datetime import datetime, timedelta

def _change_vip_dns(domain: str, new_ip: str):
    #### 更换 dns或者vip

def _send_success_msg(new_ip: str, old_ip: str, cluster_alias: str, orc_command: str = ''):
    command_tag = f'[{orc_command}]' if orc_command else ''
    action_detail = {
        '': f'实例 {cluster_alias} 发生自动容灾切换,{old_ip} => {new_ip}'
    }

def _process(new_ip: str, old_ip: str, cluster_alias: str, orc_command: str = ''):
    _change_vip_dns(domain, new_ip)
    _send_success_msg(new_ip, old_ip, orc_command=orc_command)

if __name__ == '__main__':
    log.debug(f'auto_recover 参数个数为:{len(sys.argv)}')
    log.debug(f'auto_recover 参数详情:{sys.argv}')
    _process(new_ip=sys.argv[1], old_ip=sys.argv[2],
             cluster_alias=sys.argv[3], orc_command=sys.argv[4])

14.4 PostUnsuccessfulFailoverProcesses.py

流程尝试将原故障节点的 read_only 重新设置为 false,但是前面的处理已经将故障主节点 kill 掉,所以这部分处理理应永远执行失败。这时候理应报警然后人工介入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FileName: PostUnsuccessfulFailoverProcesses.py
Description:
"""
import sys

def rollback_readonly(old_ip: str):
    try:
        with mysql_connect(ip=old_ip, user=user, password=password) as mysql:
            mysql.execute('set global read_only=0;')
    except Exception as err:
        log.error(f'集群切换时失败,并且尝试将原主read_only 回退失败')

def _process(old_ip: str):
    """
    Args:
        old_ip:
    Returns:
    """
    rollback_readonly(old_ip=old_ip)
    log.error(f'集群主节点发生异常,但进行切换时失败,请检查集群状态是否正常')

if __name__ == '__main__':
    log.debug(f' post_unsuccessful_failover 参数个数为:{len(sys.argv)} , 参数详情:{sys.argv}')
    _process(old_ip=sys.argv[1])

十五、Orchestrator 踩坑经验分享

15.1 主从版本不一样会导致无法切换

建一个实例,把备升级到 8.0.35,执行切换,会导致切换失败。

失败原因: 发现在故障切换流程中会对版本进行校验检查,如果是集群中有不同版本,有跟主库同版本的 slave 会选择到此 slave,这个选择是在选择另一个选 comaster 之前,导致选择的潜在迁移节点跟配置的 comaster 不一致导致切换失败。

如何避免: 尽量不要在同一个实例中保持两种版本。

15.2 MySQL 复制异常情况,主库宕机会发生切换

MySQL 实例如果存在备库和从库的情况下,并且复制都异常了,然后刚好主库有问题,会导致进行切换。因为备库和从库的复制都异常了,然后主库异常,导致 orch 进行了切换。

这时,可能会导致数据不一致的故障。

如何避免: 在切换的过程中需要判断 sql 线程是不是 yes,但是需要覆盖所有版本的实例进行验证,避免有一些情况,复制没有异常,但是主库宕机了,导致 sql 线程被更新为 no。

15.3 集群 GTID 复制不统一

当集群中有一个 slave 未开启 auto_position,orchestrator 会走 Pseudo-GTID 模式的切换,但是我们又没有开启 Pseudo-GTID 功能,从而导致失败。

auto_position 被设置为 0。

可以在 orchestrator 元数据库里边查询这种类型的节点,然后去对应节点通过 CHANGE MASTERauto_position 改回 1。

1
SELECT * FROM database_instance WHERE oracle_gtid=0 \G

十六、总结

Orchestrator 作为 GitHub 团队开源的 MySQL 高可用管理工具,在 GTID 时代具有显著优势。本文从以下多个维度对 Orchestrator 进行了全面介绍:

  1. 核心特性:Discover(发现)、Refactoring(重构)、Recover(恢复)三大能力构成了 Orchestrator 的核心。
  2. 架构选择:生产环境推荐使用多点非共享架构,开启 Raft 保证自身高可用。
  3. 故障检测:通过多角度验证(主库连接 + 从库反馈)避免误判。
  4. 配置体系:丰富的配置项覆盖探测、恢复、Hook 等各个方面。
  5. 切换流程:支持自动切换和手动切换两种模式,通过 Hook 机制实现高度自定义。
  6. ACK 机制:通过阻塞周期避免抖动引发的级联故障。
  7. 敏感度调优:通过 InstancePollSecondsReasonableInstanceCheckSeconds 调节对异常的容忍度。
  8. 踩坑经验:版本不一致、复制异常、GTID 配置不统一是常见的坑点,需要在部署前做好规范。

在实际生产中使用 Orchestrator 时,建议:

  • 确保集群内 MySQL 版本一致
  • 确保所有节点都开启了 GTID 和 auto_position
  • 根据业务场景合理调整故障检测敏感度
  • 完善各 Hook 脚本,做好报警、DNS/VIP 切换、连接清理等工作
  • 通过 API 与自动化运维平台集成,实现集群的自动化管理
  • 定期在 orchestrator 元数据库中查询异常节点信息