RDS MySQL云原生中间件在网易数帆的实践
导读:近几年,K8s、Cloud Native 这两个概念正风驰电掣的渗透到各个行业领域中,越来越多的企业正在投入云原生的怀抱,而与此同时,MySQL 推出了一个更为自动化的集群高可用管理方案 MGR
导读:近几年,K8s、Cloud Native 这两个概念正风驰电掣的渗透到各个行业领域中,越来越多的企业正在投入云原生的怀抱,而与此同时,MySQL 推出了一个更为自动化的集群高可用管理方案 MGR(MySQL Group Replication)。本次分享结合网易数帆基础软件实践,探讨基于 MGR 和 K8s 的 RDS MySQL 云原生中间件的一些内部实现细节,以及 K8s 能够给我们带来的优势。 01 MGR 简介1. MySQL 复制架构演进 在介绍 MGR(MySQL Group Replication) 前,首先来看看 MySQL 复制架构的进化史。 在2008年以前,MySQL 官方推出了基于 statement 和 row 的异步复制。由于是异步复制,因此都无法保证数据的一致性和完整性。所以,在2010年推出了基于 semi-sync 插件的半同步复制,不过也不能保证数据的一致性。 在2013年对半同步复制做了一些增强,包括 slave 的 crash-safe 特性,基于 GTID 的复制使得在搭建复制时不用再去人为寻找 binlog filename 以及 binlog position 位点,同时为了减小主从间的数据延时,引入了多线程回放(Multi-Thread Slave)以及组提交技术,不过组提交只支持在单个 schema 内,同时由于一些实现上的缺陷,导致并行回放形同虚设。 所以,在2015年发布的5.7.9版本中,MySQL引入了同库多线程回放,支持 Commit-Parent-Based 和 Logical_clock 两种并行模式,同时对半同步复制插件进行了一些增强,提供了 after_sync 和 after_commit 两种模式,如果设置为 after_sync 则可以保证从库上的数据不会丢失,在该版本也引入了分布式事务。对于传统的基于半同步复制的 MySQL 集群,集群中节点的角色以他们之间的复制关系均需要自己去实现一套管控逻辑来处理,对于数据的一致性保证也是耗时耗力。终于在2016年底,社区推出了基于 Paxos 分布式一致性协议的 MGR 同步复制机制,让 MySQL 有了真正意义上的集群概念。 2. MGR 模块架构 MGR 模块在 MySQL 中处于的位置与大家熟悉的半同步复制插件一样,在 server 层和引擎层之间,也是通过插件的方式提供,这让 MGR 部署比较灵活。 虽然采用了分布式一致性协议,但是在存储层和常规的 MySQL 并没有区别,每个节点都有一份完整的数据拷贝,仍然是 Shared Nothing 的架构,整体 MGR 模块的核心就是基于 Paxos 协议实现的 xcom 组件,通过这个组件 MySQL 能够实现事务的全局排序以及数据的强一致保证(最终一致性),目前 Paxos 协议有很多种不同的实现优化方案,而 MySQL 则选择了一种叫做 Mencius(孟子) Paxos 的改进版本,MySQL 为什么要选择 Mencius Paxos 呢? 首先来看看最常见的两种 Paxos 协议的实现版本:Basic Paxos 和 Multi Paxos: 而 Mencius Paxos 则采用了另外一种实现方式,将 Leader 节点的压力分摊至集群中所有的节点上,每个节点都有一个自己的提议周期,在自己的周期内可以向集群进行 Propose,如果没有要提议的内容,则直接发起 Nop(No Operation) 消息跳过本轮的提议,因此 Mencius Paxos 并没有传统的 Leader 选主的过程,不过由于是轮流发起提议,为了达成消息的全局排序,所以集群的瓶颈从 Leader 节点转移到了集群中最慢的那个节点上,这也是为什么现阶段 MGR 对于网络要求较高的原因。 当用户在 MGR 集群中提供事务时,在 server 层面与普通的 MySQL 一样没有任何差别,当进入到 commit 阶段时,该事务会被 MGR 插件的消息 hook 拦截。进入到 MGR 的流程中,MGR 插件会将事务以及 gtid 的一些信息打包成 paxos 消息,再将该消息通过 xcom 组件向集群内成员发起提议,当集群中有大多数节点接受该提议后,这个事务在集群内的顺序就达成一致,因此接下来的 certify 流程就在本地直接完成,不再需要与其它节点进行交互。整个 certify 流程是基于 writeset 消息集和当前的 gtid_executed 信息来判断事务是否可以提交或者需要回滚。通过 certify 流程后进入到 relaylog 的生成和回放流程,这一步与普通的半同步复制没有差别。 3. MGR 模块基本特性 了解了 MGR 大致的工作机制后,来总结一下 MGR 的几个基本特性: ·成员节点管理: 1. 成员角色:提供了 Primary 和 Secondary 两种; 2. 成员视图管理:包括了成员角色以及成员状态,最多支持9个节点; ·自动的 failover 故障修复(单主模式): 1. 自动选主:按启动节点 -> 权重 -> uuid 排序的规则进行选主,不经过 paxos 的 election; 2. 自动的网络分区检测; 3. 异常退出保护机制,防止双写; ·流控:保证集群节点间的延时尽可能小; ·约束: 1. 目前只支持 InnoDB 存储引擎; 2. 表中必须有主键或者唯一索引; 当然 MGR 还有其它一些约束条件可参考官方网站的说明。 02 网易数帆对 MGR 的优化 可以说网易是国内第一批在线上业务使用 MGR 的公司,当时线上业务最多有170+套 MGR 集群,因此我们在使用过程中也遇到了不少问题,同时也做了不少针对性的优化,其中最为主要的就是针对内存的优化: 1. 网易在使用 MGR 遇到的问题 MGR 相对于半同步复制来说内存使用就会更多一些,而且我们是在基于 KVM 的云主机内部署 MGR 集群,CPU 和内存资源都非常的有限,所以当业务压力增大时很容易出现 tps 不稳定以及 OOM 的情况,原因主要有以下两个方面: Writeset 缓存过大的原因分以下两类: 1. 修改记录时,根据表中定义的主键、唯一索引以及外键约束,都会生成两条 writeset 记录,因此当一个表中存在多个唯一索引,同时更新的数据集又很大时,生成的 writeset 数量就非常庞大了; 2. 在官方的实现中,writeset 缓存的清理逻辑依赖于集群内部全局广播的 gtid_executed 信息,由于广播周期默认为60秒,并且不可设置,因此当压力过大时会造成短时间内大量 writeset 无法被清理创建 OOM; 另一个导致内存被消耗光的原因就是 xcom 组件实现缺陷和操作系统的问题,主要有以下几个方面: 1. Paxos 消息缓存,这些消息缓存主要用于其它节点的 learn 请求,首先内核层面其实是有限制这个缓存的大小,并且不可配置,但是由于实现问题,这个大小会突破限制,导致内存占用过高; 2. 由于在 Paxos 集群内的所有消息必须保证全局排序,因此当有一个比其它节点都慢的节点时,有可能后面的提议会比这个慢节点的提议更早达成大多数回应,这时那些快的节点就必须要等这个慢节点的提议达到一致,如果超过一定时间,就会发起 learn 消息尝试从其它节点学习这个提议是否已达成大多数回应,不过由于这个 learn 消息的重试间隔很短,大概1秒钟会尝试5次左右,如果正好这个要学习的消息比较大,就会直接造成网络和内存的血崩,造成 OOM; 3. Linux 系统层面的内存泄露,在比较早的 debian7 版本,用于 RPC 消息序列化的组件 XDR 会有内存泄露的问题; 2. 网易在使用 MGR 上的优化 针对以上几个问题,网易数帆数据库内核团队做了一些优化: ·冲突检测数据库 由于线上部署的 MySQL 内核版本均是一致的,因此不存在 collation 校验的历史遗留问题,所以 writeset 的生成只需要保留 collation 版本,这样缓存数量直接减少一半; 现在我们线上使用的单主模式的 MGR,只有主节点能够写入数据,因此并不会存在事务冲突的问题,因此 writeset 的清理周期可以不用依赖 gtid_executed 的广播时间,所以我们可以把这个清理周期通过参数设置的更短一些防止缓存过大; 同时我们也将 writeset 缓存纳入了流控的范围,当 writeset 缓存超过一定大小,会触发流控,限制主节点上的写入速度; 不过需要注意的是,这些优化仅针对单主模式的 MGR。 ·Paxos 消息层 消息缓存的大小支持参数配置,在部署的时候可根据云主机或者容器运行内存的大小进行初始化; 针对大事务,我们增大了 learn 消息的重试周期,防止在短时间内因消息过大而造成的血崩; 升级操作系统版本至 Debian9 及以上; 除了对内存的优化,网易数帆还针对 MGR 的故障修复、死锁、部署成本(支持日志节点和投票节点)等问题进行了一系列的优化,有兴趣的同学可以在网易数据库内核专栏()里了解详细的实现方式。有一个稳定的内核版本是我们能够将 MGR 进行容器化部署的前提。 03 轻舟中间件平台1. K8s 带来的技术红利 Docker 和 K8s 从开源至今,极大地推动了云原生技术的发展,使得越来越多的企业开始拥抱云原生,并且现在均已成为各自领域的事实标准。在 K8s 开源不久,网易杭州研究院就开始探索基于 K8s 的容器平台,最早于2015年上线了容器云平台,并于2018年将其更名为轻舟容器平台NCS,现在是网易数帆旗下轻舟云原生平台的核心组件之一。K8s 和容器能给我们带来的技术红利有以下几个方面: K8s虽然拥有如此强大的能力,但它也不是万能的。首先 K8s 并不是一个传统的 PaaS 平台,它并没有提供中间件服务、数据处理框架,比如 Flink、Spark,当然也没有提供像 MySQL 这样的数据库服务,对于一些复杂的有状态应用,K8s 提供的基础功能还远远不够。不过借助于 K8s 强大的扩展机制,我们能够将这些复杂应用的管控逻辑以及运维实践经验都沉淀为一个可运行在 K8s 上的中间件。 2. 基于 Operator 构建的 PaaS 中间件 与市面上大多数 K8s 中间件一样,我们采用 Operator 来开发 RDS on K8s 中间件。在 K8s 的设计理念中,声明式(Declarative)代替了传统的命令式(Imperative)。相比于大家熟悉的命令式,用户发一个命令,服务器执行这个命令并返回结果,而声明式则是用户通过 Custom Resource 对一组资源定义一个期望的状态,Operator 则会通过一系列的操作,将这组资源达到用户所期望的状态,这也是 K8s 系统强大的自愈和自治能力所遵从一个理念。所以结合一个稳定的 MGR 内核版本以及 Operator 提供的强大高效的开发框架,我们就可以在 K8s 上部署 MGR 集群并实现实时的监控和故障自愈等功能。 04 RDS MySQL 中间件实践1. 基于 Operator 开发的 RDS MySQL 中间件 基于 Operator 开发的 RDS MySQL 中间件可以构建在标准的 K8s 集群上,通过 K8s 的 API 对相关的资源进行操作,主要分为控制面(Operator)和数据面(MGR、数据访问、故障自愈)两个层面。 控制面 Operator 通过 Helm 工具打包并安装至 K8s 集群中,部署方式为3副本部署,利用 K8s 的 etcd 进行选主,控制面主要负责以下几个方面: Pod 作为 K8s 中最小的可部署计算单元,是部署 MGR 集群必不可少的资源。但是,Pod 本身是个无状态的资源,而 MySQL 是一个有状态的应用,因此我们借助了 K8s 中提供的另外一种资源类型:StatefulSet。 StatefulSet 这种资源其实是一组有状态的 Pod(挂载了数据盘)的合集,其中 Pod 的副本数可以指定,不过我们并没有采用一个 StatefulSet 中定义多个 Pod 副本数的方式来实现 MGR 集群,因为 StatefulSet 本身有自己的故障自愈和更新逻辑,这些内置的逻辑对于我们的 Operator 来说是不可控的,很有可能会造成一些不可预知的行为(比如无法指定顺序的 RollingUpdate),所以为了能够让 Operator 实现自主可控,我们采用了 MGR 集群内每个节点对应一个副本的 StatefulSet 来实现。 这里我们主要还是想复用 StatefulSet 对于磁盘的管理能力以及单个 Pod 的自愈能力,而其余的 MGR 相关的升级以及故障自愈逻辑都能够在 Operator 中实现。在一个 Pod 里,我们内置了两个容器,一个是 MySQL 容器,MySQL 相关的安装包以及必要的工具均安装在这个容器里,另一个是 MySQL-Exporter 容器,由 Prometheus 官方提供,用于对 MySQL 相关的运行时数据进行监控,同时我们也能够集成用户自定义的容器(比如第三方系统对于数据库元数据信息采集的需求)。 在数据面的访问层,我们直接使用了 K8s 提供的 Service 资源,基于 iptables 或者 ipvs 的代理模式,提供了 K8s 集群内和集群外的多种访问方式,同时具备适配不同云厂商负责均衡的能力。在数据访问这一层,我们实现了 MGR 上的读写分离,对于只读请求,提供了两种模式,一对一只读和一对多只读: 2. MGR 在 K8s 上的心跳监测以及故障自愈相关的实现方案 接下来介绍一下 MGR 在 K8s 上的心跳监测以及故障自愈相关的实现方案。首先是集群运行状态的监测,这里的处理逻辑和 K8s 的声明式理念一致,通过 K8s 的 SPDY 流通道与 MGR 集群内的所有 Pod 建立连接云主机mysql,Operator 按一定的时间间隔去 Pod 上获取心跳信息,不断的重复收集 - 分析 - 修复三个步骤: 1. 心跳收集: (编辑:威海站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |