我们使用 Percona XtraDB Cluster 作为 MySQL 集群方案,它是 multi-master 的 MySQL 架构,实例间基于 Galera Replication 技术实现数据的实时同步。这种集群方案可以避免 master-slave 架构的集群在主从切换时可能出现的数据丢失现象,进一步提升数据的可靠性。
备份方面,我们使用 xtrabackup 作为备份/恢复方案,实现数据的热备份,在备份期间不影响用户对集群的正常访问。
提供「定时备份」的同时,我们也提供「手动备份」,以满足业务对备份数据的需求。
2. 服务高可用
这里从「数据链路」和「控制链路」两个角度来分析。
「数据链路」是用户访问 MySQL 服务的链路,我们使用 3 主节点的 MySQL 集群方案,通过 TLB(七牛自研的四层负载均衡服务)对用户提供访问入口。TLB 既实现了访问层面对 MySQL 实例的负载均衡,又实现了对服务的健康检测,自动摘除异常的节点,同时在节点恢复时自动加入该节点。如下图:
基于上述 MySQL 集群方案和 TLB,一个或两个节点的异常不会影响用户对 MySQL 集群的正常访问,确保 MySQL 服务的高可用。
「控制链路」是 MySQL 集群的管理链路,分为两个层面:
全局控制管理
每个 MySQL 集群的控制管理
全局控制管理主要负责「创建/删除集群」「管理所有 MySQL 集群状态」等,基于 Operator 的理念来实现。每个 MySQL 集群有一个控制器,负责该集群的「任务调度」「健康检测」「故障自动处理」等。
这种拆解将每个集群的管理工作下放到每个集群中,降低了集群间控制链路的相互干扰,同时又减轻了全局控制器的压力。
如下图:
这里简单介绍下 Operator 的理念和实现。
Operator 是 CoreOS 公司提出的一个概念,用来创建、配置、管理复杂应用,由两部分构成:
Resource
自定义资源
为用户提供一种简单的方式描述对服务的期望
Controller
创建 Resource
监听 Resource 的变更,用来实现用户对服务的期望
工作流程如下图所示:
即:
注册 CR(CustomResource)资源
监听 CR objects 的变更
用户对该 CR 资源进行 CREATE/UPDATE/DELETE 操作
触发相应的 handler 进行处理
我们根据实践,对开发 Operator 做了如下抽象:
CR 抽象为这样的结构体:
对 CR ADD/UPDATE/DELETE events 的操作,抽象为如下接口:
在上述抽象的基础上,七牛提供了一个简单的 Operator 框架,透明化了创建 CR、监听 CR events 等的操作,将开发 Operator 的工作变的更为简单。
我们开发了 MySQL Operator 和 MySQL Data Operator,分别用来负责「创建/删除集群」和「手动备份/恢复」工作。
由于每个 MySQL 集群会有多种类型的任务逻辑,如「数据备份」「数据恢复」「健康检测」「故障自动处理」等,这些逻辑的并发执行可能会引发异常,故需要任务调度器来协调任务的执行,Controller 起到的就是这方面的作用:
通过 Controller 和各类 Worker,每个 MySQL 集群实现了自运维。
在「健康检测」方面,我们实现了两种机制:
被动检测
主动检测
「被动检测」是每个 MySQL 实例向 Controller 汇报健康状态,「主动检测」是由 Controller 请求每个 MySQL 实例的健康状态。这两种机制相互补充,提升健康检测的可靠度和及时性。
对于健康检测的数据,Controller 和 Operator 均会使用,如下图所示:
Controller 使用健康检测数据是为了及时发现 MySQL 集群的异常,并做相应的故障处理,故需要准确、及时的健康状态信息。它在内存中维护所有 MySQL 实例的状态,根据「主动检测」和「被动检测」的结果更新实例状态并做相应的处理。
Operator 使用健康检测数据是为了向外界反映 MySQL 集群的运行情况,并在 Controller 异常时介入到 MySQL 集群的故障处理中。
在实践中,由于健康检测的频率相对较高,会产生大量的健康状态,若每个健康状态都被持久化,那么 Operator 和 APIServer 均会承受巨大的访问压力。由于这些健康状态仅最新的数据有意义,故在 Controller 层面将待向 Operator 汇报的健康状态插入到一个有限容量的 Queue 中,当 Queue 满时,旧的健康状态将被丢弃。
当 Controller 检测到 MySQL 集群异常时,将会进行故障自动处理。
先定义故障处理原则:
不丢数据
尽可能不影响可用性
对于已知的、能够处理的故障进行自动处理
对于未知的、不能够处理的故障不自动处理,人工介入
在故障处理中,有这些关键问题:
故障类型有哪些
如何及时检测和感知故障
当前是否出现了故障
出现的故障是哪种故障类型
如何进行处理
针对上述关键问题,我们定义了 3 种级别的集群状态:
Green
可以对外服务
运行节点数量符合预期
Yellow
可以对外服务
运行节点数量不符合预期
Red
不能对外服务
同时针对每个 mysqld 节点,定义了如下状态:
Green
节点在运行
节点在 MySQL 集群中
Yellow
节点在运行
节点不在 MySQL 集群中
Red-clean
节点优雅退出
Red-unclean
节点非优雅退出
Unknown
节点状态不可知
Controller 收集到所有 MySQL 节点状态后,会根据这些节点的状态推算 MySQL 集群的状态。当检测到 MySQL 集群状态不是 Green 时,会触发「故障处理」逻辑,该逻辑会根据已知的故障处理方案进行处理。若该故障类型未知,人工介入处理。整个流程如下图:
由于每种应用的故障场景和处理方案不同,这里不再叙述具体的处理方法。
3. 易使用
我们基于 Operator 的理念实现了高可靠的 MySQL 服务,为用户定义了两类资源,即 QiniuMySQL 和 QiniuMySQLData。前者描述用户对 MySQL 集群的配置,后者描述手动备份/恢复数据的任务,这里以 QiniuMySQL 为例。
用户可通过如下简单的 yaml 文件触发创建 MySQL 集群的操作:
在集群创建好后,用户可通过该 CR object 的 status 字段获取集群状态:
这里再引入一个概念:Helm。
Helm 是为 Kubernetes 提供的包管理工具,通过将应用打包为 Chart,标准化了 Kubernetes 应用的交付、部署和使用流程。
Chart 本质上是 Kubernetes yaml 文件和参数文件的集合,这样可以通过一个 Chart 文件进行应用的交付。Helm 通过操作 Chart,可一键部署、升级应用。
由于篇幅原因及 Helm 操作的通用性,这里不再描述具体的使用过程。
4. 易运维
除了上述实现的「健康检测」「故障自动处理」以及通过 Helm 管理应用的交付、部署,在运维过程中还有如下问题需要考虑: