高可用HA(High Availability) 是分布式系统架构设计中必须考虑的因素之一。本篇文章主要介绍主从模型服务的高可用演进,包括以下几个方面:
高可用(Hign Availability)是分布式系统架构设计中必须要考虑的因素之一。系统可用性的评估计算公式如下:
通常的描述如下表所示:
主从(Master-Slave)模型,核心思想其实就是分而治之。在主从模型中,一般都是读写分离的,读写操作互相不影响且能避免机器出现性能瓶颈。这时只有Master对外提供写能力,Slave只提供读能力,Master和Slave之间进行通过网络进行数据同步。当Master节点宕机或者不可用时,从Slave中选择一台机器晋升为Master继续对外提供服务,从而实现服务的高可用。
而在微服务架构中,一般我们的应用服务都是无状态的,应用服务之间无需数据同步、也无需选主,可通过简单的水平扩展来实现提高服务的可用性。
对于有状态的系统,如果要实现高可用,那么不可避免的就需要采用主从模型,如MySQL、Redis、Hadoop等相关存储类型产品,又比如ZooKeeper和一些MQ产品。
今天我们探讨的就是分布式系统中主从模型高可用的实践。
任何一个高可用的架构都不是一蹴而就的,系统可用性的提升都是一个持续发现问题、解决问题、循序渐进提升的过程。当业务量较小的时候,一个单体架构的系统就能够满足需求,但是随着业务的增长相应架构会逐渐暴露出可用性等问题,然后去优化解决,最终整个系统架构在能够支撑业务的情况下达到极高的可用性。
架构的演进本质上都是为了解决问题,一般来说主从高可用架构都会经历如下几个阶段:
任何大型系统的发展都伴随着系统架构的演进,系统初始阶段一般都是单应用设计,所以场景零如下图所示:
此时,服务处于最原始的形态,所有的功能都在一个应用中实现,并且只有一台机器对外提供读写服务。处于 All In One 的阶段。
这个时候所面临的问题就是单点问题,即一旦该机器宕机或者网络隔离无法访问,就会面临着服务不可用的情况。
为了解决场景一中的单点问题,对系统架构进行了调整和升级,实现了最简单的主从模型。如下图所示:
图中展示的主从架构模型中,有一台Master机器和两台Slave机器。Master机器对外提供写能力,Slave机器对外提供读能力,Master和Slave通过网络进行数据同步。
在当前主从架构中首先对服务进行了读写分离改造,并且数据有多个冗余副本,可用性有一定提高。但是当出现机器不可用或者出现网络隔离时需要手动切换节点,所以此时所面临的问题是 如何实现自动Failover ,即当Master不可用时,服务如何自动恢复,并继续对外提供服务;当Slave不可用时,如何自动踢出。
为了实现服务的自动Failover,我们引入了控制中心,如下图所示Admin节点:
引入控制中心后,Master和Slave启动时都会在控制中心中进行注册,然后控制中心通过心跳和Master与Slave进行通信,以此来判断节点是否存活。同时控制中心还控制着Master节点的选取,当Master节点宕机后,从Slave节点中进行选主,选择一个Slave节点晋升为Master。最简单的选主方式就是通过注册顺序进行选主,首先服务启动时选择第一个注册的节点晋升为Master,当Master挂掉后节点删除,根据注册顺序依次选择Slave节点晋升成为Master。
此时如果Slave节点故障后,会自动将Slave节点踢出。如果Master节点挂掉后会进行重新选主,在两个Slave节点中选择一个新的Master节点,此时是一主一从,继续对外提供服务;
此时架构所面临的问题就是 脑裂 。脑裂是指在一个高可用系统中,相互连接的节点因为网络隔离断开连接,本来为一个整体的系统,分裂成了两个独立集群。这时两个集群开始争抢共享资源,导致数据不一致、系统混乱等问题。
上面的解释相对比较抽象,我们来分析下控制中心判决Master节点故障的情况,由于控制中心和Master节点是通过心跳来判活,所以当两者之间出现网络隔离时控制中心也会认为Master节点挂掉,所以其实Master节点故障的原因可能是:
所以当Master节点只是由于网络隔离导致被控制中心认为不可用时,那么就会存在以下的情况:
一旦出现脑裂,那么就势必就造成数据不一致、丢失或者严重引起整个集群不可用等问题。
脑裂问题可以从软件层面和硬件层面两个维度去解决,主要有如下几种思路:
硬件层面的解决方案就是硬件的冗余,比如多个节点之间多布几条网线或者专线,达到网线级别的冗余。这种方案缺点在于成本太高。
软件层面这里我们通过引入租约机制来解决脑裂问题,如下图所示:
租约的定义是:租约是由颁发者授予的在某一有效期内的承诺。租约有以下几个特点:
在我们引入租约机制后,大致的选主流程为:Master节点申请租约,在租约时间范围内承认其Master的角色,当租约过期后需要续约,维持自己Master的角色;如果租约过期后没有续约,那么就退出Master角色,重新选主。更加详细的租约机制读者可自行查阅。
图中Master和Slave节点上方都有一条红线代表租约的有效期时间,当租约过期后Master节点会继续申请租约。如果这个时候Master节点因为是网络隔离导致不可用了会出现什么情况呢?可以预见的是,如果Master节点被网络隔离,租约过期后肯定不能续约,那么就会退出Master角色,重新选主;重新选主后即使网络恢复原有Master节点也蜕化为了Slave节点,不会导致脑裂问题。
再来看一个极端的例子,如果是Master节点刚申请完租约成功后就因为网络原因被隔离或者挂掉,这种情况怎么处理?由于此时是在租约过期后才会触发重新选主,所以在这段租约时间内,写服务不可用了(读服务依然可用,因为Slave节点没挂),只能等待租约过期重新选主;这就是租约会导致的问题:服务会有一个最大不可用时间,取决于租约时间。工程中,常选择的租约时长是10s级别。
这里其实笔者看了下公司内部以及开源的一些中间件系统(一些,特定场景),发现有些系统没有刻意去解决脑裂问题,交流了下发现设计原因如下:
脑裂出现的概率极低,但是对于不能丢数据的服务一旦出现就是致命的。现在各个大厂都会在机房之间布多条专线,硬件级别冗余来保证可靠性。
现在系统架构存在的问题和场景一的问题一致: admin控制中心的单点问题
通过租约或者硬件冗余解决了脑裂问题后,应用服务就能够达到较高的可用性,但是现在admin控制节点又面临着场景零时会遇到的问题:admin中心控制节点的单点问题。
如图所示,依然是场景零的解决方案,通过引入admin中心控制备机来解决控制中心的单点问题。
当然,现在也会面临着场景零所面对的相同问题——主备问题:如何实现控制中心的自动failover。
另外在当前架构下,除了中心控制备机自身自动failover的问题,还存在少数的admin判决master太过于暴力。
虽然我们引入了中心控制备机,一定程度解决了admin的可用性问题。但实际上对服务存活情况的判决还是由主admin这单一节点来决定的。这时如果是admin节点自己出现负载高,以至于处理Master的心跳被延迟了,可能会导致admin误判Master没有发送心跳,重新选主。
另外就是如今的服务都会要求多机房部署,如果对于服务的存活判断是由一个admin决定,那么出现admin节点与服务机器跨机房部署时,对于其他机房甚至异地机房服务的判活极容易出现误判,网络的抖动或者服务不稳定原因就可能会引起频繁的选主。
场景四中引发了两个问题,我们先来看第一个:控制中心的主备问题。在场景二中我们通过引入控制中心节点解决了应用服务的主备问题,但是为了解决控制中心的主备问题,我们不能再引用一个更高层次的控制中心,否则架构演进上就会出现死循环。
所以控制中心只能通过自己来解决主备问题,通常的做法是依赖外部的选主算法,如paxos或者raft等协议。即控制中心自己实现paxos或者raft等选主算法,来解决自身的主备问题。
其实这里大家也都清楚了,我们一直讨论的admin的角色,就是日常使用的Zookeeper所扮演的角色。在这里可以直接将admin集群用zk集群替代。
解决了自身的选主和自动failover后,对于第二个问题:少数admin判决master太过暴力,这时引入控制中心多点仲裁机制,如下图所示:
多点仲裁机制通俗点讲就是少数服从多数的机制。就是由多个admin共同判决服务是否存活,少数服从多数。如上图,每一个admin节点都会分别和应用节点建立连接,对其中任何一个节点是否存活的判断,都需要大多数admin节点“达成一致”。这时候如果master节点和其中一台admin节点因为网络隔离断开,因为另外两台admin节点还保持连接,所以还是会认为master节点存活。
如果是每个admin都和所有的应用节点建立连接,当节点数量过多时每个admin都会维护海量连接,存在非常大的性能开销。所以这里Zookeeper采用的是共享Session的机制,一个应用节点只会和一个admin节点建立连接,服务和admin建立连接初始化Session后,admin集群就会共享这个Session,即使与服务连接的admin节点故障,如果应用服务短时间内携带该Session的信息请求其他admin节点,还是会认为是同一个连接。
现在的架构就能够满足绝大多数的场景了,但是当服务数量进一步增多时,还会面临如下两个问题:
场景五架构中存在着网络风暴和admin控制中心瓶颈两个问题,网络风暴可以在实践中通过好的实现方案来规避,在实现分布式锁是案例中也有提到。
对于admin控制中心瓶颈的问题,我们通过引入控制中心数据分布方案来解决,如下图所示:
此时的架构中,实际上就是进行数据分片,由多个集群共同管理元数据。如上图将单一的admin集群进行了拆分,形成了两个admin集群,同时通过super admin来进行协调和数据的同步。通过集群拆分和数据分片达到数据分布的效果,减少admin自身的压力。
当前架构中我们使用了super admin节点来进行协调和数据的同步。还有一种数据分片方案,是redis集群采用的,使用gossip协议进行节点间的数据同步,有兴趣的大家可自行查阅。
这种架构下,就能够有效的支撑业务量较大的场景。但是也存在着如下问题:
如果admin集群是提供了全地域或者全组织的协调服务,那么当其出现故障后,就会导致全国所有地域或者整个公司服务都不可用的情况。
当公司内部服务到达一定规模后,就应该按照功能或者组织进行拆分,并且跨地域部署单独的集群,集群物理隔离。不仅有助于提高整个系统的稳定性,即使故障也能够将故障范围控制在单一地域和集群。此时架构如下所示:
此时的架构能够很好的支撑各自业务场景,但是对于公司的运维能力有着极大的考验。 在这个架构下,各大公司都会有一套完善的运维监控体系,包括并不限于对机器状态指标的监控,对节点状态同步的监控,对数据快照大小的监控,对观察者数量的监控。