分布式一致性与容错共识算法

77 阅读11分钟

名词解释

  • 一致性指的是状态一致,因此一致性问题仅存在于有状态服务,这样的服务有数据库(MySQL, Redis)、配置中心(ETCD、Zookeeper)、消息队列(Kafka、RocketMQ)等。
  • 共识指的是不同节点间就某一事件达成一致,例如对数据库某一个键值对的修改。

共识问题从何而来

为什么需要分布式架构

一切需要从分布式架构开始说起,最初软件服务使用单机架构时,因为状态有且仅有一份,所以此时不存在一致性问题。随着业务的发展,单机已经不能满足需求,因此引入了备份,即把数据拷贝多份,分别维护在不同的独立的机器上,它主要有三方面作用:

  • 可伸缩性、高性能:将数据、读取负载、写入负载等分散到多台计算机上,提供更高性能的服务。
  • 容错 / 高可用性:使用多台机器,以提供冗余。一台故障时,另一台可以接管。
  • 延迟:如果在世界各地都有用户,那么在全球范围部署多个服务器,从而每个用户可以从地理上最近的数据中心获取服务,使用户有更好的服务体验。

常见分布式架构

主从

  1. 其中一个副本被指定为 领导者(leader) 。当客户端要向数据库写入时,它必须将请求发送给该 领导者,其会将新数据写入其本地存储。
  2. 其他副本被称为 追随者(followers) ,每当领导者将新数据写入本地存储时,它也会将数据变更发送给所有的追随者。每个跟随者从领导者拉取日志,并相应更新其本地数据库副本,方法是按照与领导者相同的处理顺序来进行所有写入。
  3. 当客户想要从数据库中读取数据时,它可以向领导者或任一追随者进行查询。但只有领导者才能接受写入操作(从客户端的角度来看从库都是只读的)。

多主

多主架构是主从架构的自然延伸,它允许多个节点接受写入。复制仍然以同样的方式发生:处理写入的每个节点都必须将该数据变更转发给所有其他节点。在这种情况下,每个主库同时是其他主库的从库。使用多主架构的场景主要是多数据中心。

在每个数据中心内使用常规的主从复制;在数据中心之间,每个数据中心的主库都会将其更改复制到其他数据中心的主库中。

每个数据中心通常位于不同的地理位置,用于给地理上临近的用户提供高性能的服务。由于不同数据中心间地理上相距较远,出于性能的考虑,只能分别接受写入请求,再异步同步到其他数据中心。这会涉及如何解决写入冲突。

除此之外,协同编辑也可以本质上也可以看作多主架构。

无主

分布式带来的问题

  • 网络不可靠:当你尝试通过网络发送数据包时,数据包可能会丢失或任意延迟。同样,答复可能会丢失或延迟,所以如果你没有得到答复,你不知道消息是否发送成功了。
  • 节点故障或暂停: 机器可能会遭遇硬件损坏或机房断电等故障;一个进程可能会在其执行的任何时候暂停一段相当长的时间(可能是因为停止所有处理的垃圾收集器)

一致性模型

共识就是让所有的节点对某件事达成一致,有两种常用的一致性模型:

最终一致性

大多数复制的数据库至少提供了最终一致性,这意味着如果你停止向数据库写入数据并等待一段不确定的时间,那么最终所有的读取请求都会返回相同的值。

这是一个非常弱的保证 —— 它并没有说什么时候副本会收敛。在收敛之前,读操作可能会返回任何东西或什么都没有。例如,如果你写入了一个值,然后立即再次读取,这并不能保证你能看到刚才写入的值,因为读请求可能会被路由到另外的副本上

线性一致性

基本的想法是让一个系统看起来好像只有一个数据副本。在一个线性一致的系统中,只要一个客户端成功完成写操作,所有客户端从数据库中读取数据必须能够看到刚刚写入的值。这是最强的一致性保证

依赖线性一致性的场景主要是分布式锁和选主,诸如 Apache ZooKeeper 和 etcd 之类的协调服务通常用于实现分布式锁和领导者选举

共识算法的目标就是使得系统在能容忍部分故障的基础上,给应用提供线性一致性服务。

容错共识算法

最著名的容错共识算法是 视图戳复制(VSR, Viewstamped Replication) ,代表性的有Paxos ,Raft 以及 Zab。这些算法实现了全局顺序一致的消息,每条消息只传递一次并正确传送到每个节点。

算法在实现线性一致的基础上还要满足容错的要求,因此写入操作不能等待所有节点响应后返回。

容错共识算法假设不存在 拜占庭式错误, 也就是说系统每个节点都会严格遵守协议约定。

单主架构算法-Raft

单主架构是最常用的架构,Raft是基于单主架构最有影响也最常用的共识算法之一。Raft算法极大地受到Paxos算法的影响,实现了与Paxos一样的能力与性能,但更易于理解,同时提供了完整的实现代码和论文,使得Raft更容易在工程中落地,ETCD就是基于Raft协议开发的线性一致KV存储。

每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。每一个服务器日志都按照相同的顺序包含相同的指令,所以每一个服务器都执行相同的指令序列。因为每个状态机都是确定的,每一次执行操作都产生相同的状态和同样的序列。

一致性算法的任务是保证复制日志的一致性。Leader将日志推送到Followers,Followers按相同的顺序应用日志的指令,从而保证状态的全局一致。

法定人数

无论是选主还是写入,都要从 法定人数(quorum) 的节点中获取选票,法定人数由多数节点组成。例如一个有5个节点的系统,大于或等于3个节点就是多数。在Raft中,当一个写入操作得到法定人数节点响应时就可以提交。

法定人数机制使得Leader必定始终包含最新提交的日志,也就是不会因为切换Leader而丢日志。因为上一轮写入操作成功响应的节点中,必定有一个能在下一次选主中获得多数选票(这里有一个隐含条件,各节点在投票时会判断候选者所拥有的日志是否比自己的新),换句话说,如果一个节点不包含最新日志,则不可能成为Leader。

如何避免脑裂

脑裂是指系统中有两个或以上节点都认为自己是Leader,它们都接受客户端的写入操作使数据损坏。出现原因一般是Leader宕机后重新恢复加入集群,或是集群中网络中断产生网络分区。

Raft使用任期编号(term)来标识每一次选主后的任期,term是全序且单调递增的。如果两个不同的时代的Leader之间出现冲突(也许是因为前任领导者实际上并未死亡),那么带有更高的term的Leader说了算。

Raft的实现过程是,当Leader宕机后随即会选举出一任新Leader,此时宕机的节点恢复了过来仍认为自己是Leader,继续向其余节点发送心跳请求,请求参数中携带term号,其他节点收到后发现请求的term号落后于自己,则把自己的term号返回。故障恢复的节点发现自己的term号落后了,便自动退回Follower角色。

选主

Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者身份。一个服务器节点继续保持着跟随者状态只要他从领导人或者候选人处接收到有效的 RPCs。领导人周期性的向所有跟随者发送心跳包来维持自己的权威。如果一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,那么他就会认为系统中没有可用的领导人,并且发起选举以选出新的领导人。

选主过程可视化

当一个候选人从整个集群的大多数服务器节点获得了针对同一个任期号的选票,那么他就赢得了这次选举并成为领导人。

每一个服务器最多会对一个任期号投出一张选票,按照先来先服务的原则。

要求大多数选票的规则确保了最多只会有一个候选人赢得此次选举(图 3 中的选举安全性)。一旦候选人赢得选举,他就立即成为领导人。然后他会向其他的服务器发送心跳消息来建立自己的权威并且阻止发起新的选举。

状态复制

一旦一个领导人被选举出来,他就开始为客户端提供服务。客户端的每一个请求都包含一条被复制状态机执行的指令。领导人把这条指令作为一条新的日志条目附加到日志中去,然后并行地发起附加条目 RPCs 给其他的服务器,让他们复制这条日志条目。如果跟随者崩溃或者运行缓慢,再或者网络丢包,领导人会不断的重复尝试附加日志条目 RPCs 直到所有的跟随者都最终存储了所有的日志条目。

在 Raft 算法中,领导人是通过强制跟随者直接复制自己的日志来处理不一致问题的。这意味着在跟随者中的冲突的日志条目会被领导人的日志覆盖。5.4 节会阐述如何通过增加一些限制来使得这样的操作是安全的。

要使得跟随者的日志进入和自己一致的状态,领导人必须找到最后两者达成一致的地方,然后删除跟随者从那个点之后的所有日志条目,并发送自己在那个点之后的日志给跟随者。所有的这些操作都在进行附加日志 RPCs 的一致性检查时完成。

线性读

Raft 中的客户端发送所有请求给领导人。当客户端启动的时候,他会随机挑选一个服务器进行通信。如果客户端第一次挑选的服务器不是领导人,那么那个服务器会拒绝客户端的请求并且提供他最近接收到的领导人的信息。

但响应客户端请求的领导人可能在他不知道的时候已经被新的领导人取代了,因此Raft在响应只读请求之前,先和集群中的大多数节点交换一次心跳信息来处理这个问题。

总结

用容错共识算法实现线性一致的代价和局限性主要有:

  • 牺牲了系统的性能,因此很多分布式数据库选择完全异步复制的方式
  • 当Leader故障或网络断开时,系统将进行选主,这段时间系统将处于短暂的不可用状态
  • Raft、Paxos、Zab这类共识算法最多只能容忍不超过半数节点的故障,否则系统将不可用

参考资料

Raft paper

DDIA

MIT 6.824: Lectures 6 & 7 - Fault Tolerance(Raft)

Chapter 8 - The Trouble with Distributed Systems

Raft Consensus Algorithm

hardcore.feishu.cn/docs/doccnM…