00 出品人说
在数据库集群规模迅速扩大的背景下,如果出现故障,如何快速恢复成百甚至数千个集群的数据和服务,是很多大型互联网企业面临的重要挑战。线上部署了几十万的微服务,数据库结构和拓扑随时在发生变更,系统重构、内核升级、硬件设备汰换、机房搬迁等等,也都会对数据库的稳定工作产生一定的影响。作为整个IT系统中为重要、为底层的服务,即便遇到了极小概率事件的冲击,也会造成非常大的影响。对美团数据库团队来说,“低垂的果实已经摘完”,我们开始着力应对这些小概率事件对业务造成的冲击。
数据库稳定性保障的破局之道:一方面是提升平均无故障间隔(MTTF),另一方面是提升应急响应能力,即缩短平均修复时间(MTTR)。在这两个目标的指引下,美团数据库团队从能力驱动和故障驱动两个维度来构造整个稳定性保障的闭环体系。
从能力驱动的角度,我们借鉴了Google的稳定性保障体系。在底部的三层,通过故障演练/预案建设、复盘、可观测性的维度,思考怎么缩短故障处理时长;中间四层更多的是围绕研发需求、设计、上线、变更管控来降低故障的发生概率;顶层是产品运营,即通过面向内部用户的运营,指导业务对数据库进行选型和合理的使用,不断提升产品和平台易用性,并针对业务特点提供相应的解决方案。
01 高可用简介
| 1.1 面临的挑战
首先分享下美团数据库高可用面临的问题和挑战,主要从3个层面进行展开:
个挑战是实例增长越来越快。下图1截取了2019年1月到2022年1月的数据,可以明显地看到实例规模的增长非常迅速,在大规模场景下,如何保证每一个实例的高可用性是一个非常大的挑战。大家都知道,保障几台机器稳定运行,跟保障几万台甚至几十万台机器的稳定运行,其复杂度完全不在一个量级。
第三个挑战是容灾场景的复杂性。容灾场景主要分成三个层面,个是常规容灾,比如日常软件、硬件或者网络故障;第二个是AZ容灾,即机房层面,如机房断网、机房宕机等;第三个是Region容灾,即更大空间容灾,典型的是城市级容灾,目前主要还在解决AZ级容灾,分如下五个阶段:
从图4可以看到,我们将AZ容灾分设第0至第4共5个阶段,简称L0-L4。随着等级的提高,场景越来越复杂,相应的规模也越大。从容灾规模维度看,单点->单个集群->某个业务依赖的集群->AZ内的集群,不同规模要求的能力是完全不一样的,除了规模之外还有容灾的场景也会在变化。
“L0-L1”这两个等级侧重面向常规容灾,是实例级容灾。 “L2-L3”这两个等级侧重面向AZ容灾,相比L1有非常大的跨越,因为既要解决“L0-L1”面临的常规容灾问题,还要解决一个很核心的问题,即整高可用自身是否能够快速恢复,以及高可用依赖的下游服务是否具备容灾切换能力。由于高可用本身是一个系统,它有数据面和控制面,有上下游依赖,所以先保证自己是可用的,才能保证数据库的RTO和RPO。 L4,从L3到L4又有一个很大的跨越,因为L3的规模是相对可控的,而L4直接是断AZ的网络,AZ的大小不同,它的规模更大,更贴近真实的AZ故障。
| 1.2 发展历程
接下来,分享一下美团高可用系统发展历程。总的来说,美团的高可用发展历程是根据不同的阶段的矛盾和挑战,做出的相应解决策略和方案。到目前为止,有三次比较大的系统架构迭代:
代架构是2015年之前,称为MMM(Multi-Master Replication Manager for MySQL),该架构包括接入层VIP、Agent和Manager,这架构本身存在很多问题,比如VIP接入无法支持跨机房、跨网段;Agent和实例绑死,本身也没有高可用,维护起来比较困难;Manager还是单点。 第二代架构是2015-2019年,称为MMHA(Meituan Master High Availability),是在MHA的基础上结合美团数据库生态定制的架构,解决了代架构中VIP接入和Agent误判的一些问题,但在2019年以后,由于实例规模变得很大且增长迅速,该架构管理起来异常复杂,Manager是单点没有解决自身的高可用。同时,2019年整个PAAS在演练机房故障,所以当时这个架构逐渐暴露出各种稳定性问题。 第三代架构是2019年,新开了基于Orchestrator定制的高可用系统,实际开始时间是2018年,2019年开始灰度。当时MGR在业界已经有些公司在使用,MGR通过分布式协议来解决高可用难题,我们对此进行了深入的思考:我们是否能引入一个类似分布式协议解决高可用系统面临的问题,本质上就是把MGR中内置的分布式协议放到高可用系统实现。
后来根据这个想法落地的架构就是当前的高可用系统,是一个Raft Group,多节点、多机房部署。以前主要关注主从切换,但新系统类似八爪鱼,所有在集群里的节点都是统一托管,如主库、从库、Ripple(后续有说明)等;另一方面,它是一个高可用、高性能、大规模并发处理架构,我们部署了多个Raft Group分组来托管不同地域和服务等级数据库集群。除此之外,我们也正在思考新一代去中心化的新架构,将在文章的后一个章节进行介绍。
02 高可用部署
| 2.1 高可用架构(数据流、控制流)
这部分主要分两条线:控制流和数据流。
数据流:如果业务应用要访问数据库,它是如何拿到数据的,一个SQL过来是如何把数据返回回去的,业务应用通过访问中间件看到MySQL的拓扑,数据流比较简单,也是业界比较通用的做法。 控制流:业务应用要通过访问中间件来访问到正常数据库拓扑,需要用高可用组件来解决故障转移难题。
由下图6可看出,高可用组件分四个部分:HA Core是经典的3节点Raft Group部署(也可以是多节点部署);HA平台是管控系统,如主动切换、状态机查看、兜底等;HAservice是HA的API服务,负责跟外围系统交付;调度系统包括(Scheduler、Worker),是一个流程调度服务,处理状态机的轮转。元数据存储是外围依赖的核心服务,负责基础配置的管理,当配置变更后会通过它下发到所有的中间件节点,保证业务看到的是正常的数据库拓扑。
| 2.2 高可用部署(HA Core、微服务、数据层)
下面我们围绕4个高可用组件来展开介绍一下,高可用部署在应对AZ级容灾或Region级容灾的策略。
一是HA Core部署,它有三个特征:一是多Region,如下图6所示,以红色线为分界,左边是Region 1,右边是Region 2;二是多AZ,每一个HA Core是3AZ部署;三是多集群,每个HA Core的3节点集群会托管MySQL千级别实例。
二是微服务,有同步服务、调度服务和配置中心。
同步服务:简单来说,我们的工程师在RDS申请集群或DB之后的信息会全部同步注册到HA Core服务里面去,相当于HA Core是一个“八爪鱼”,它能发现这些信息。 任务调度:它也包括API Service、Scheduler和Worker,主要做状态机任务执行。这两个服务都是多Region、多机房部署,它们本身没有状态。 配置中心:业务应用访问中间件和高可用之间的数据同步纽带,是MySQL的节点发现和处理的核心组件,是双Region部署,有自己的组件,比如有API、Config和Consistency。
三是数据层,微服务除了配置中心,其他服务没有状态,因为状态都在数据层,数据层服务都是MGR集群,而且这些集群各自作用不一样,MGR是单Region写、多Region读,所以这里我们回到开始说的Region容灾,这些数据需要做单元化或者隔离,现在这些集群还是单Region写的。
03 重点模块设计
| 3.1 故障发现(减少漏判、降低误判)
故障发现有两个核心指标:
个指标是不要漏判,如果故障没有判断,那RTO其实根本就不生效。 第二个指标是降低误判,因为我们知道RTO不可能等于0,即RTO一定对业务有影响,如果一天误判几次,那对业务是无法接受的,对业务是有损的。
先说漏判,如下图8所示的故障探测通道,每个探测通道完全独立,如普通探测通道,心跳探测通道、从库探测通道等完全独立决策,谁先发现都可以作为决策依据,现阶段这些通道都是在服务端主动并发发起探测。另外一方面就是业务端,访问中间件有业务端的报错,也纳入故障探测和决策,这个是业务视角的故障,不管什么问题只要业务达到有损阈值就可以判断决策,这部分还在建设中。综合多通道、以及客户端和服务端判定,哪一个通道发现了问题,直接开始做决策,不受其他通道影响,这是解决漏判问题的策略。
再说减少误判,我们引入了多节点协商机制,比如我们遇到某些问题会引入“多数派决策”决策让大家发挥更多意见,终汇总得出结论。HA Core是多节点,当每个节点独立探测,探测后也会独立去做判断和分析,如只有一个Follower节点认为故障是没用的,还需要Leader的分析并向其他Follower节点发起协商共同决策,只有大多数节点认为故障才会把它确定为故障,会注册故障并进行故障处理流程。图中的Backend DB是Raft Group每个节点各自独立的存储,各自探测的拓扑以及判定决策信息等都存在本地。
| 3.2 故障选举(选举因子、选举策略)
所谓故障选举一定是多个从库,一主一从不存在选举。美团的MySQL集群现状是一主多从,所以选举异常复杂,因为我们要保证容灾N+1、多AZ甚至多Region部署,选举时选谁做主库就非常重要,主要有两个影响因素:选举因子和选举策略。选举因子+选举策略 = 决定谁是新主。
(1)选举因子是影响选举的核心要素,一次故障有20多个选举因子会共同影响如何排序。
举两个例子,个是版本,一主五从的集群可能有三个版本,如果让新的版本作为新主就会有问题,要尽量不让它作新主,因为如果新主的版本是新的,但从库都是比他落后的低版本,就会出现一些兼容性问题;第二个是权重,假设其他因子都一样,但存在运维和例行维护的情况下会人为把权重降低,或标记一个实例不能作为新主,那么权重100的比权重50的有更大机会成为新主。每个选举因子都有权重,终做综合排序,如版本、binlog格式、权重、服务器配置等,综合选举因子的选举结果是1M(first master)。
但1M并不一定是优,有业务是北上跨Region部署,如之前主库在北京,按照选举因子排序选出的1M到上海去了,显然是业务无法接受的,因为老主库在北京说明绝大部分业务部署在北京,一旦让它跨Region写入到上海,那么RT会增加很多,所以引入了选举策略。
(2)选举策略是同机房优先>同中心优先>同区域优先,是一个灵活的fallback策略。按照选举策略重新选举一个新主(称为2M),如果2M和1M是重叠的,就认为这个1M是满足业务诉求的Master,会将1M作为终的主库。但有时候1M和2M的排序相差很大,这时我们尽量让那种没办法改变的因素以它为基础,把其它能改变的因素对齐,比如地域很难改变,但是位点等容易改变,终权衡后选举出新主库。
举例:
如图11(主库M,从库S1、S2、S3、S4四个实例),S2的选举规则(promotion rule)是一票否决的Must Not,那它一定不能做主库,即使选不出来其他,它也做不了主库。 S1和老主库是同AZ的即都是AZ1,S1比AZ2的S3和S4有更高优先级,即更大的机会作为新主。 S4权重100,S3权重90,S4比S3权重更高,即使S4是独享容器,它也有更高的选举权。
| 3.3 数据一致性(四个风险及解决方案)
为什么要保证数据一致?在我们现在这种规模的业务场景下,可用性优先策略已经没办法覆盖所有的业务场景,但是在主从架构下面,数据丢失又无处不在,参考图12为例,数据丢失的风险点比较多:
问题1,可能binlog未实时落盘。 问题2,IO线程没拿到新数据。 问题3,SQL线程没有和IO线程对齐,包括从库之间binlog位点也不一样。 问题4,从库不完整事务问题,在主库事务提交时它是完整的,但是它通过IO线程同步给从库时不是按事务粒度去同步而是按事件event粒度同步,如果事务未完整接收也可能会产生数据丢失不一致。
针对问题3和问题4,其实归纳一下就是说只要有从库拿到数据,不管是否对齐,我们是有策略能够保证一致性,这个策略叫S1,只要数据同步过去了则就可以通过S1保证一致性。 针对问题2,就是主库Event,没有任何一个从库有获取全,这种情况必须解析老主库binlog以及计算binlog位点并获取到数据,并在切换中补齐数据,即在开放给业务写流量之前,会将数据给新主库补全保证一致性,这个策略叫S2。 但S1+S2也不能保证0RPO,服务器宕机时拿不到老主库binlog,没有办法计算、解析和处理,不能保证0RPO,大概有20%到30%的比例。
接下来,讲一下针对老主库宕机拿不到数据时的解决方案(这个叫策略S3)。在此,也参考MySQL主从库架构通过IO线程和SQL线程去解决一致性的思路。个难点,IO线程要尽量把数据拿全,这是非常关键的点,也是很难的事情;第二个难点,SQL线程能够快速把数据应用下去,达到这两方面才能保证0RPO。
个难点解决方案如图13所示,核心是Ripple,它是一个高性能的binlog订阅服务器,作用类似IO线程,保证快速的获取binlog。个特性是它的性能极高,因为普通从库IO线程除了处理binlog接收请求外,也处理很多比如写Relay log、一些锁逻辑处理,以及配合SQL线程一致性的工作,而Ripple的任务就是快速将数据全部拿过来而不用处理其它逻辑,binlog同步速度是普通MySQL IO线程的3倍以上;第二个特性是可以配置强一致,支持半同步机制,如果你认为可以牺牲RTO,比如可以牺牲3分钟但不能丢失数据,那么配置多副本的半同步超时策略(如3分钟),由于Ripple的性能高,实际超时时间远远小于这个值,退化概率非常低;第三个特性,它本身是一个存算分离的架构,即Ripple是轻量级容器+EBS云盘,用这样一种架构来保证数据完全。
第二个难点是事中从Ripple补数据,Ripple毕竟不是普通的MySQL,所以需要HA去兼容,但HA处理数据比SQL线程灵活很多,因为SQL线程在需要实时处理数据来保证一致性,而HA只有在故障期间才处理数据,可以拉长RTO做很多定制。还有这里提到的RPO≈0,为什么是近似,因为虽然可以做到RPO=0,但要接受RTO的损耗。如果你既要RTO,又要RPO,那么可以在策略灵活配置。
可用性优先:可根据业务特性自定义,承诺RTO不保证RPO。 一致性优先:可根据业务特性自定义,保证RPO但不会无限制,RTO会控制上限。 不可控因素:事务大小、延迟、事务完整性等。
| 3.4 多机房高可用
常规场景,Leader故障后会在4秒之内快速选举一个新Leader出来继续工作,但也有一些场景,如下图15所示,MySQL Master和HA 的Leader都在AZ1,如果AZ1宕机之后怎么办?其实就出现右边这个图。老Leader在处理AZ1里的MySQL节点故障的同时,由于自身Leader也在AZ1,会中断老Leader的处理状态机并选举一个Leader,但新Leader并不知道老Leader状态机如何处理,就直接导致切换的失败。
多机房高可用的的核心解决思路是保证各个节点之间状态机的连续性。所谓状态机的连续性,就是Leader实时把状态机同步到所有Follower节点,一旦Follower节点重新变为Leader点后,它会继续老Leader的状态机,会有动作的临界状态判断。如下图15所示,它根据执行的代价判断是回滚还是继续执行,如果回滚,就会把老Leader状态机全部回滚之后从零开始处理;另外一方面,如果由于集群的拓扑和状态被破坏了,导致回滚状态机比较麻烦或回滚不了,那么就会继续执行老Leader状态机没有执行完的动作。
保证多节点状态机连续性:
状态同步:Leader实时将状态机通过Raft同步到所有的Follower节点 临界状态:根据执行代价确定的状态机临界点 回滚:新Leader执行状态机回滚,包括对应的操作 继续:新Leader继续执行老Leader的状态机
| 3.5 配置下发(双Region下发)
由于配置服务是双Region部署,分为同Region下发和跨Region下发。同Region下发会写到存储层,而config-server会更新新配置,将配置并推送到客户端。跨Region下发,比如说北京、上海和深圳都有业务服务节点,终把它推送下去的时候也会走一致性*服务(Consistency-Server),即一旦某个Region更新后,我们会把数据推送到另一个Region里,Region之间的数据完全一致,如果另外一个的Region有业务服务节点,就会继续走同Region下发流程。
04 未来思考
后,分享一下对高可用未来的一些思考,主要包括以下三个方面:
提升容灾能力,主要是AZ级容灾和Region级容灾。这两方面我们还在建设中,AZ级容灾需要减少依赖,不能减少的需要AZ级闭环独立部署,以及提升大规模并发处理的能力等;Region级容灾方面,我们在做一些单元化的思考和方案,尽量让包括数据层等所有服务闭环,不要跨区域访问。 去中心化架构。业界也有数据库把高可用内置到MySQL,如MGR,内置到MySQL有非常多的优势,但对于美团一主多从架构是现状前提。另外,MGR架构对网络抖动的容忍度较低,以及对请求延时有一些增加,导致大部分业务场景没法接受。所以,我们在做另外一种思路,即把高可用内置到Proxy进程,让Proxy自带数据库高可用的能力,跟内置到MySQL的思路类似。 去依赖化、集群化。将HA的Service、Scheduler和Worker及配置中心等下游依赖去掉,希望内置到Proxy进程后,内部数据通过Raft/Gossip协议同步而不再依赖中心化服务,让它完全做集群化,这也是我们2023年在思考的策略。