既要考虑发生分区时需要保证的是CP还是还是AP,还需要考虑在没有发生分区时,如何保证CA的问题。
我们可以在分区故障解决时,让系统重新恢复CA状态。
以用户系统为例,假如我们选择了CP,则分区发生后,节点1能继续写入数据注册新用户,节点2不能注册新用户,因为返回error,这就是不符合A。此时节点1可以将注册但未同步到节点2的用户记录到日志中。当分区恢复以后,节点1读取日志中的记录,同步给节点2。当同步完成时,节点1和2就同时达到了CA状态。
同样的,如果我们选择了AP,那么节点1和节点2都可以修改用户信息,但是修改的结果不一样。比如,节点1中修改为美食,节点2修改为旅游,节点1和节点2都记录了未同步的修改信息,当分区恢复时,系统需要按某种规则合并数据。也可以将冲突报告出来,由人工解决。(有点类似于Git)
设计分布式系统的两大初衷:横向扩展(scalability)和高可用性(availability)。“横向扩展”是为了解决单点瓶颈问题,进而保证高并发量下的「可用性」;“高可用性”是为了解决单点故障(SPOF)问题,进而保证部分节点故障时的「可用性」。由此可以看出,分布式系统的核心诉求就是「可用性」。这个「可用性」正是 CAP 中的 A:用户访问系统时,可以在合理的时间内得到合理的响应。
为了保证「可用性」,一个分布式系统通常由多个节点组成。这些节点各自维护一份数据,但是不管用户访问到哪个节点,原则上都应该读取到相同的数据。为了达到这个效果,一个节点收到写入请求更新自己的数据后,必须将数据同步到其他节点,以保证各个节点的数据「一致性」。这个「一致性」正是 CAP 中的 C:用户访问系统时,可以读取到最近写入的数据。
需要注意的是:CAP 并没有考虑数据同步的耗时,所以现实中的分布式系统,理论上无法保证任何时刻的绝对「一致性」;不同业务系统对上述耗时的敏感度不同。
分布式系统中,节点之间的数据同步是基于网络的。由于网络本身固有的不可靠属性,极端情况下会出现网络不可用的情况,进而将网络两端的节点孤立开来,这就是所谓的“网络分区”现象。“网络分区”理论上是无法避免的,虽然实际发生的概率较低、时长较短。没有发生“网络分区”时,系统可以做到同时保证「一致性」和「可用性」。
发生“网络分区”时,系统中多个节点的数据一定是不一致的,但是可以选择对用户表现出「一致性」,代价是牺牲「可用性」:将未能同步得到新数据的部分节点置为“不可用状态”,访问到这些节点的用户显然感知到系统是不可用的。发生“网络分区”时,系统也可以选择「可用性」,此时系统中各个节点都是可用的,只是返回给用户的数据是不一致的。这里的选择,就是 CAP 中的 P。
分布式系统理论上一定会存在 P,所以理论上只能做到 CP 或 AP。如果套用 CAP 中离散的 C/A/P 的概念,理论上没有 P 的只可能是单点(子)系统,所以理论上可以做到 CA。但是单点(子)系统并不是分布式系统,所以其实并不在 CAP 理论的描述范围内。
image.pngimage.png
从集中式和非集中式这两种分布式集群架构的网络分区形态可以看出,要判断是否形成网络分区,最朴素的方法就是判断节点之间心跳是否超时,然后将心跳可达的节点归属到一个子集中。由于非集中式系统中,每个节点都是对等的、提供的服务相同,所以当多个子集群之间不可达,或部分节点出现故障后,尽管提供的服务质量(SLA)可能会下降,但并不影响这些剩余节点或子集群对外提供服务。
网络分区最微妙的地方在哪里?在工作和生活中遇到一个问题,你的本能反应估计是,有问题就解决问题好了。而网络分区最微妙的地方在于,你很难通过程序去判断问题到底出在哪里,而只能通过心跳等手段知道部分节点的网络不可达了。但,导致节点不可达的原因有很多,有可能是网络的原因,也有可能是节点本身的问题。也就是说,我们无法通过一些症状就判断出是否真的产生了分区。另外,你也很难通过程序去判断这个问题是不是很快就会被恢复。这也是应对网络分区问题最微妙的地方。
我们说的网络分区肯定是就同一个集群而言的。对于不同集群来说,正是因为集群间本就没有太多的交互,才需要从逻辑上分割成不同的集群,这些逻辑上不同的集群本就是可以独立对外提供服务的。
网络分区有哪些常见的处理方法?为了不影响分布式系统的高可用性,检测到网络分区后,我们就需要尽快地进行处理。假如,我们采用一种非常激进的方式去处理,即一旦发现节点不可达,则将不可达节点从现有集群中剔除,并在这个新集群中选出新的主。以图 1 所示集中式集群为例,当备 Master、Slave3 和 Slave4 节点检测到主 Master、Slave1 和 Slave2 节点不可达时,剔除这些不可达节点后,备 Master 升主,连同 Slave3 和 Slave4 节点组成一个新的集群。如果不可达是由于节点故障导致的,那么这种策略没有任何问题。这些剩余节点组成的集群可以继续对外提供服务。但,如果不可达是因为网络故障引起的,那么集群中的另一个子集,即主 Master、Slave1 和 Slave2,也会采用同样的策略,仍然对外提供服务。这时,集群就会出现双主问题了
方法一:通过 Static Quorum 处理网络分区Static Quorum 是一种固定票数的策略。
在系统启动之前,先设置一个固定票数。当发生网络分区后,如果一个分区中的节点数大于等于这个固定票数,则该分区为活动分区。为了保证发生分区后,不会出现多个活动分区,导致出现双主或多主的问题,需要对固定票数的取值进行一些约束,既:固定票数≤ 总节点数≤2* 固定票数 - 1。这个策略的优点是,简单、容易实现,但却存在两个问题:一是,对于分区数比较少的时候,比方 2 个分区时,该策略很容易选出一个唯一的活动分区。但是,当活动分区非常多的时候,由于各个分区的票数分散,不容易找到一个满足条件的分区,没有活动分区也就意味着整个集群不可用了。二是,由于这种策略里固定票数是固定不变的,所以不适用于集群中有动态节点加入的场景。
方法二:通过 Keep Majority 处理网络分区顾名思义,Keep Majority 就是保留具备大多数节点的子集群。由于不限定每个分区的节点数超过一个固定的票数,所以可以应用于动态节点加入的场景。假设,集群数为 n,出现网络分区后,保留的子集群为节点数 w≥n/2 的集群。为防止出现双主或两个集群同时工作的情况,通常将集群总节点数 n 设置为奇数。可想而知,若集群总数为偶数,比如图 1 集中式架构的例子中,子集群 1 和 2 都包含 2 个 Slave 节点,就会导致两个子集群同时存活,在业务场景只允许一个主的情况下,会导致业务运行不正确。那么,如果真的出现了集群总节点数为偶数,两个子集群节点数均为总数一半时,又应该如何解决分区问题呢?这时,我们可以在 Keep Majority 的基础上,叠加一些策略,比如保留集群节点 ID 最小的节点所在的子集群。如图 1 所示,假设集群节点总数为 6,现在因为网络故障形成网络分区子集群 1{主 Master,Slave1, Slave2}和子集群 2{备 Master,Slave3, Slave4},假设 Slave1 是 ID 最小的节点,那么此时要保留包含 Slave1 的子集群 1。虽然 Keep Majority 方法可以解决动态节点加入的问题,但也不适用于产生多分区的场景。因为随着分区数增多、节点分散,也难以在众多分区中出现一个节点数 w≥n/2 的分区。前面我讲到集群跨多个网络部署时更容易产生网络分区,因此我不推荐采用 Static Quorum 和 Keep Majority 方法去处理跨多网络集群的网络分区问题。
方法三:通过设置仲裁机制处理网络分区设置仲裁机制的核心是,引入一个第三方组件或节点作为仲裁者,该仲裁者可以与集群中的所有节点相连接,集群中所有节点将自己的心跳信息上报给这个中心节点。因此,该中心节点拥有全局心跳信息,可以根据全局心跳信息判断出有多少个分区。当出现网络分区后,由仲裁者确定保留哪个子集群,舍弃哪些子集群。如下图所示,假设引入 Node0 作为第三个节点,该节点 IP 为 10.12.24.35,当出现网络分区子集群 1{Node1, Node3}和子集群 2{Node2, Node4}时,每个子集群中选择一个 Leader 节点并 ping 一下 Node0 的 IP,能 ping 通则保留,否则舍弃。比如下图中,子集群 1 可以 ping 通,子集群 2 ping 不通,因此保留子集群 1。
方法四:基于共享资源的方式处理网络分区说到共享资源,我们需要先回顾下分布式锁的相关知识。分布式锁是实现多个进程有序、避免冲突地访问共享资源的一种方式。基于共享资源处理网络分区的核心,其实就类似于分布式锁的机制。也就是,哪个子集群获得共享资源的锁,就保留该子集群。获得锁的集群提供服务,只有当该集群释放锁之后,其他集群才可以获取锁。关于锁的管理和获取,你可以再回顾下第 7 篇文章中的相关内容。这种方式的问题是,如果获取锁的节点发生故障,但未释放锁,会导致其他子集群不可用。因此,这种方式适用于获取锁的节点可靠性有一定保证的场景。基于仲裁和共享资源的网络分区处理方法,其实都是依赖一个三方的节点或组件,然后借助这个第三方来保证系统中同时只有一个活动分区。所以,这两种处理方法适用于有一个稳定可靠的三方节点或组件的场景。
关于网络分区的处理方法,其本质就是,在产生分区后,选出一个分区,保证同时最多有一个分区对外提供服务。基于此,我为你梳理了四种常见的处理方法,包括 Static Quorum、Keep Majority、设置仲裁机制和基于共享资源的方式。其中,基于 Static Quorum 的方法,因为涉及固定票数策略,所以不适用于处理多个分区,以及有动态节点加入的场景;基于 Keep Majority 的方法,可以解决动态节点场景下分区问题,但因为要求子集群节点数≥1/2 总节点数,所以也不适用于处理多个分区的情况;而基于仲裁和共享资源的网络分区处理方法,其实都是依赖一个三方的节点或组件,所以适用于有一个稳定可靠的三方节点或组件的场景。
一个电商网站核心模块有会员,订单,商品,支付,促销管理等。
对于会员模块,包括登录,个人设置,个人订单,购物车,收藏夹等,这些模块保证AP,数据短时间不一致不影响使用。
订单模块的下单付款扣减库存操作是整个系统的核心,我觉得CA都需要保证,在极端情况下牺牲P是可以的。
商品模块的商品上下架和库存管理保证CP,搜索功能因为本身就不是实时性非常高的模块,所以保证AP就可以了。
促销是短时间的数据不一致,结果就是优惠信息看不到,但是已有的优惠要保证可用,而且优惠可以提前预计算,所以可以保证AP
现在大部分的电商网站对于支付这一块是独立的系统,或者使用第三方的支付宝,微信。其实CAP是由第三方来保证的,支付系统是一个对CAP要求极高的系统,C是必须要保证的,AP中A相对更重要,不能因为分区,导致所有人都不能支付
网友评论