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 页面调用。
其具有如下主要功能:
- 可以自动发现 MySQL 集群,通过 MySQL 节点信息获取相关 slave 的信息,进而获取整个集群拓扑结构并将信息存储在后端数据库中。
- 基于复制集管理规则,提供自由重构复制集拓扑结构的功能,如将某个 slave 节点移动到其他 master 下。
- 自动探测故障并采取相应恢复措施,实现 MySQL 服务高可用的目的,减少人工介入。
- 提供 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(作为从库后边又接有从库)是否故障,避免由于网络问题导致判断失误。
通常情况下通过以下两点来确定是否发生故障:
- 是否可以连接到主库
- 是否可以连接到所有从库,并且确认它们是否可以连接到它们的主库
6.2 给定一个节点如何自动发现整个集群拓扑#
给定主库,如何发现从库:
如果 DiscoverByShowSlaveHosts 参数为 true,则会尝试先通过 SHOW SLAVE HOSTS 命令去发现从库。
- 从库设置了正确的
report_host,SHOW SLAVE HOSTS 中的 host 字段显示正确的 IP,则直接通过 SHOW SLAVE HOSTS 发现从库。 - 从库设置了错误的
report_host,SHOW SLAVE HOSTS 中的 host 字段显示错误的 IP,则 orchestrator 找不到从库。
如果 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 流程。
- 在
OnFailureDetectionProcesses Hook 中获取此次故障的类型(自动/手动),对于自动切换的场景发出主库故障报警。 - Orchestrator 开始对集群进行切换,主要动作为:选出新主,关闭新主 read_only,修改旧的 slave 主从关系指向新主。
- Orchestrator 开始进行故障切换后的工作,通过
PostFailoverProcesses Hook 实现,主要动作为:修改 DNS 指向,或者 VIP 指向,发出切换完成通知。 - DBA 对旧主故障处理完成后,手动将旧主加回到集群。
10.2 手动切换#
手动切换由人工通过 web 页面/API 调用/命令行发起,此时 Orchestrator 节点会跳过故障探测。
- 在
OnFailureDetectionProcesses Hook 中获取此次故障的类型(自动/手动),对于手动切换的场景,跳过故障报警。 - Orchestrator 开始对集群进行切换,主要动作为:将旧主设置为 read_only,将旧主、旧 slave 的主从关系改为指向新主,关闭新主的 read_only。
- 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 recover 或 orchestrator-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 掉。
十三、如何调整故障处理敏感度#
可以从 InstancePollSeconds 和 ReasonableInstanceCheckSeconds 两个配置项调节 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 总结#
所以总结来说,调大 InstancePollSeconds 和 ReasonableInstanceCheckSeconds 参数能够降低 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 MASTER 将 auto_position 改回 1。
1
| SELECT * FROM database_instance WHERE oracle_gtid=0 \G
|
十六、总结#
Orchestrator 作为 GitHub 团队开源的 MySQL 高可用管理工具,在 GTID 时代具有显著优势。本文从以下多个维度对 Orchestrator 进行了全面介绍:
- 核心特性:Discover(发现)、Refactoring(重构)、Recover(恢复)三大能力构成了 Orchestrator 的核心。
- 架构选择:生产环境推荐使用多点非共享架构,开启 Raft 保证自身高可用。
- 故障检测:通过多角度验证(主库连接 + 从库反馈)避免误判。
- 配置体系:丰富的配置项覆盖探测、恢复、Hook 等各个方面。
- 切换流程:支持自动切换和手动切换两种模式,通过 Hook 机制实现高度自定义。
- ACK 机制:通过阻塞周期避免抖动引发的级联故障。
- 敏感度调优:通过
InstancePollSeconds 和 ReasonableInstanceCheckSeconds 调节对异常的容忍度。 - 踩坑经验:版本不一致、复制异常、GTID 配置不统一是常见的坑点,需要在部署前做好规范。
在实际生产中使用 Orchestrator 时,建议:
- 确保集群内 MySQL 版本一致
- 确保所有节点都开启了 GTID 和
auto_position - 根据业务场景合理调整故障检测敏感度
- 完善各 Hook 脚本,做好报警、DNS/VIP 切换、连接清理等工作
- 通过 API 与自动化运维平台集成,实现集群的自动化管理
- 定期在 orchestrator 元数据库中查询异常节点信息