最近一年左右在研究系统架构和分布式系统。今天跟大家一起分享讨论。群里有技术和其他领域背景的,所以不会讲的很深入和靠近细节。尽量让大部分人理解。
今天给大家简单分享一下几种分布式系统的一致性算法,常见的有 Paxos, Raft, ZAB。

一、分布式系统

首先简单说一下分布式系统。这是一个比较宽泛的概念。他的产生和出现是为了应对和解决以往系统的问题。
我的简单理解是一套互相连接共同向外提供服务的系统,系统内部节点间通过某种方式进行通信。常见的大的如电商网站,小的如一个数据库集群,缓存集群等。
在分布式系统出现之前使用的单机系统,一旦服务器的操作系统、硬盘、网络等出现问题,就可能会出现服务中断,数据丢失等问题。 另外,单机的性能再高,存储能力再扩展,也是有限的,而分布式系统可以包含大量的服务器用来分散带宽、存储和计算压力。 第三,由于跨较远区域甚至跨国的网络延迟有时还是客观的,所以服务器集中在一个地方很难向较大范围提供较好的服务和体验。
百度上对分布式系统的解释:

分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。

个人对这里的内聚性和透明性的理解是:

同时由于不完美的运行环境,他本身需要解决一些问题。
如:系统中使用的每个节点都可能出现单点故障突然不工作了,节点间会有或大或小、忽大忽小的网络延迟,节点间的网络也有可能出问题。 从技术角度看,可能发生的问题基本上认为早晚会发生。说到底很多问题就是概率和成本收益的平衡。 因此,一个分布式系统要正常提供对外的服务,它的设计本身就要考虑到这些问题的解决。 在算法层面就是由分布式系统的一致性算法来辅助解决和应对这些问题,确保系统尽可能的对外正常的提供服务。

这里说尽可能的意思是分布式系统不能确保永远完全没有问题,尤其是一些极端情况。 比如一个3个节点的数据库集群,单节点挂掉的概率较小,3个节点同时挂掉的概率极小极小,不过也存在这种可能,不过概率很小,一般不考虑。 或者机房停电,光缆被挖段,如果几个节点都在一个机房,那就不能工作了。 今天讲的一致性算法主要解决的是从系统的角度分析一个分布式系统内部节点本身和节点间出现问题的解决。 跟这些外部问题关联较小,那些场景要同时借助其他手段来缓解和解决。

二、一致性问题

比较容易理解的常见的实际问题: 一个系统包含5个节点,由于网络原因其中3个节点和另2个节点失去了联系,这时候系统应该怎么处理,是否还可以继续提供服务。
如果继续提供服务,他们之间的数据就是不一致的,后面节点间的网络恢复后,怎么解决数据不一致的问题。
节点间的网络延迟、磁盘读写、CPU处理能力不同,保存到这个系统中的数据在哪些情况下可以认为保存成功了,还要以一种效率较高的方式来实现。

三、ZAB协议

3.1 ZAB 的基本特点

把节点分两种,Leader(主)和Follower(从)。 有一个主节点,所有写操作全部通过节点进行处理,如果一个从节点收到了一个写操作请求,就会转给主节点处理。 其他节点都是从节点,可以通过从节点进行读操作。 主节点通过选举得出,主节点失踪后,其他从节点自动开始选举新的主节点。

注意, 这里写操作可以认为是保存或修改现有数据。读操作可以认为是读一个现存的数据出来。读操作相对简单一些,这里主要讲写操作, 其流程为:

  1. 客户向主节点请求写数据X。
  2. 主节点为该数据生成一个唯一的递增Id,叫ZXIDX(Id)。
  3. 主节点把X(Id)发给所有的从节点,跟他们确认能不能正常把数据记录下来,这个操作叫 Propose 提议;
  4. 超过一半的节点向主节点回复没问题,这个回复操作叫 ACK 应答
  5. 节点收到一半以上从节点的肯定答复后,给所有的从节点发送确认提交请求,表示你们可以把这个数据保存下来了,同时自己也正式保存这个数据,这个过程叫 Commit 提交。

现在问题来了,如果没有超过一半的从节点给主节点回复ACK。那么主节点就不能确认该消息可以成功记录下来。 为啥一定要收到超过一半的从节点的答复?答案是为了++确保数据的一致性++。 一般分布式一致性算法可以确保在部分节点挂掉的情况下保持数据的一致性,在大部分节点都同时挂掉的情况不能确保。++比如:一共5个节点,2个挂掉没问题,其中可以包括主节点,如果3个挂掉了,就不能确保了++。 一般出现这种情况时,比如5个节点只剩了2个,这两个节点就不再提供服务了。

3.3 工作流程

比如一个银行的存款系统:3个节点在上海,2个节点在广州。一个账号Adun,有1K的钱在里面。5个节点都包含这个数据。 这时上海广州之间的网络挂了。我连到上海的节点把钱转给一个人,然后连到广州的节点把里面的1K钱转给另一个人。 他们之间是断开的,如果同时提供转账服务,就出现了数据一致性的问题。 而如果只有3个节点的上海提供服务就没有这个问题了。 另一个问题是,他们之间的网络恢复后,以谁的数据为准呢?所以大部分的分布式一致性算法都是采用大多数认同的方式。 当然,在写数据的时候如果强制确认所有节点都写入了新数据会更安全和一致,但是系统的可用性和性能就大大降低了,只要有一个节点挂了,系统就不工作了。

我们分析下子节点挂掉的情况。 先看节点挂掉数占比情况分析: 少于一半的子节点挂掉没有任何影响。达到一半的子节点挂掉系统不能提供服务,一般这种情况概率很小。
一个子节点挂掉一段时间后又恢复了之后, 先通过主节点同步新的数据,因为自己挂了一段时间,很可能没有最新的数据。数据同步之后正式成为工作的子节点开始工作。 添加一个新的子节点到一个现有的集群,也是这个过程。

如果是主节点挂掉,情况会复杂一点。 主节点挂掉后,集群暂时不提供写服务,开始新的主节点选举。选举规则如下:

  1. 发消息到每一个自己还能连上的节点:包含自己的节点编号叫 myid 和 自己保存的数据的最大的 ZXID。
  2. 谁的ZXID最大,谁就是新的主节点。为什么呐?因为这表示他的数据最新。尽量确保数据一致性。
  3. ZXID相同的时候,谁的myid最大谁是新的主节点;
  4. 收到其他节点的数据后,跟自己的判断,如果对方比自己的大,就认同对方为主节点。
  5. 得到一半以上(注意这里又是一半以上) ,节点认同的候选的节点成为新的主节点。

新的主节点选举出来之后,进入集群的数据同步节点,先检查集群内部哪些节点的数据比自己旧,把数据同步过去。 然后开始向外部提供服务。 在新的主节点选出来之前,集群不能提供写服务。 这里的步骤有好几步,在正常的服务器环境下,这个过程是很快的,几十到几百毫秒。

回到刚才的上海和广州的场景。 广州的发现上海的节点失踪了,这时候开始选举新的主节点,但是它们只有2个节点,选不出来主节点,也不提供服务。上海的继续服务。网络恢复后,把数据同步到广州节点然后继续工作。

这是 ZAB 协议的主要信息。还有很多细节,感兴趣的话可以去看看。不过一般分布式系统的一致性算法都有点复杂。ZAB是为了支撑ZooKeeper服务设计出来的,ZooKeeper是给其他服务提供集群管理服务的,所以他本身要足够健壮。ZAB 协议的全称就是Zookeeper Atomic Broadcast,即 ZooKeeper 原子性广播协议。

四、Raft 算法

下面再简单介绍几句 Raft 算法。 Raft 算法很多地方跟 ZAB 很像,也分主节点和从节点,主节点选举的地方有一点差别。

  1. 发现主节点失踪一段时间后,所有从节点向其他从节点发消息,让他们选自己为新的主节点;
  2. 还没参加选举的节点如果收到其他节点的选举请求,就选举自己收到的第一个节点,后面谁再请求自己支持选举,就告诉他们我已经支持另一个节点了
  3. 如果一个节点发现另一个节点得到的支持比自己多,也就开始无条件支持那个节点选举,同时让支持自己的节点也去支持它
  4. 如果一轮没选出来得到大多数节点支持的主节点,就开始下一轮选举,直到一个节点得到了大部分节点支持,成为新的主节点;

五、Paxos 算法

还有一个叫Paxos的一致性算法,更复杂,惭愧我目前还不敢确保完全理解,Paxos 算法还衍生出来好几种变体,经典的 Paxos 算法不分主节点和从节点。
其实现在大部分的一致性算法都是起源于或参考了 Paxos 算法,有人说只有 Paxos 才是真正的一致性算法。

这就是今天的分享内容,谢谢大家。估计群里有这方面的大牛,我抛砖科普一下。有相关的问题也欢迎讨论。


Q&A

Q: 主节点本身也是选举出来的,如果主节点挂了呢?
A: 一般分布式一致性算法可以确保在部分节点挂掉的情况下保持数据的一致性,在大部分节点都同时挂掉的情况不能确保。比如一共5个节点,2个挂掉没问题,其中可以包括主节点,如果3个挂掉了,就不能确保了。一般出现这种情况时,比如5个节点只剩了2个,这两个节点就不再提供服务了。
Q: 在主节点失去心跳到选举新的主节点作为写入,这期间的数据怎么保证最终一致?
A: 主节点和从节点之间吗?
Q1: 对,“++选举规则如下:发消息到每一个自己还能连上的节点:包含自己的节点编号叫myid和自己保存的数据的最大的 ZXID。 1. 谁的ZXID最大,谁就是新的主节点。为啥?因为这表示他的数据最新。尽量确保数据一致性。2.ZXID相同的时候,谁的myid最大谁是新的主节点;3.收到其他节点的数据后,跟自己的判断,如果对方比自己的大,就认同对方为主节点;4.得到一半以上(注意这里又是一半以上);节点认同的候选的节点成为新的主节点。++”是这段话吗?
A1: 这是新的主节点选举规则,ZAB 协议里面。
Q2: 在主节点失去心跳到选举新的主节点作为写入,这期间的数据怎么保证最终一致?那还是这个问题。
A2: 主节点失联的时候不能写数据。如果此时主节点上有没有提交的数据它就挂了,那么客户端就不能得到这个消息成功写入的响应。因此系统本身的数据是一致的。主节点上没提交的数据在从节点上也是未提交,因此没提交数据的ZXID不算数。严格的讲这段时间系统是不可写的。
Q3: 嗯。一般主从节点重新选举的频率是多少?
A3: 这个要看具体情况了,跟服务器,网络,机房环境,系统本身开发的怎么样,压力这些都有关系;节点挂掉才会进行选举,不过具有主节点的系统,主节点的压力也会比较大,协议的具体实现互相差别也很大,涉及到开发细节的话还有很多参数要考虑,比如心跳间隔,节点数量等。
Q4: 是,也就是说根据参数等因素,来决定要不要在主节点还没有down时,重新进行选举!我理解的对吗?参数就是收集来的指数;
A4: 不是,选举一定是在主节点挂了之后做,判断依据的问题,比如通过心跳来判断,心跳间隔太大响应就慢,心跳间隔太小容易出现假死;
Q5: 嗯。即使压力过大,也不换?
A5: 这种小概率问题,一般需要外部系统通过缓存,延迟调用等方式解决,运维上需要对服务器做自动化监控预警告警方面的处理。


补充

  1. 这个也是区块链共识算法的基础,很多东西都是相通的;
  2. 区块链也是一种分布式系统,对一致性要求也较高,51% 算力攻击跟今天的主节点选举需要大部分节点支持类似;
  3. BTW, 下面是一个raft算法的动态交互演示,可以帮助更好的理解 http://thesecretlivesofdata.com/raft/ ;
  4. 十分钟讲清楚共识机制

作者: Adun 佟浩,现任南京一家互联网公司研发总监。研究方向主要为分布式系统架构设计、微服务、敏捷开发。
邮箱: aduntong@outlook.com
欢迎沟通交流。


本文档来自支付产品技术交流群的聊天记录整理,由志愿者整理并发布到本网站。如需要及时收到来自支付产品技术交流群的最新消息,请扫码关注“凤凰牌老熊”的微信公众号。 本群面向支付行业的有经验(2年以上)的产品经理、软件工程师、架构师等,提供交流平台。如想加入本群,请在本文评论中留言(不公开),说明所在的公司、负责的工作、入群分享的主题和时间。