引入
最早研究一致性的场景既不是大数据领域,也不是分布式系统,而是多路处理器。
可以将多路处理器理解为单机计算机系统内部的分布式场景,它有多个执行单元,每一个执行单元都有自己的存储(缓存),一个执行单元修改了自己存储中的一个数据后,这个数据在其他执行单元里面的副本就面临数据一致的问题。
随着时代发展,互联网公司的快速发展,单机系统在计算和存储方面都开始面临瓶颈,分布式是一个必然的选择,但是这也进一步放大了数据一致性面临的问题。对于数据的一致性,最理想的模型当然是表现得和一份数据完全一样,修改没有延迟,即所有的数据修改后立即被同步。但在现实世界中,数据的传播是需要时间的,所以理想的一致性模型是不存在的。
不过从应用层的角度来看,我们并不需要理想的一致性模型,只需要一致性模型能满足业务场景的需求就足够了,比如在统计分析类的场景中,是能容忍一定的误差的,而评论之类的场景中,可能只要有因果关系的操作顺序一致就可以了。
同时由于一致性要求越高,实现的难度和性能消耗就越大,所以我们可以通过评估业务场景来降低数据一致性的要求,这样人们就定义了不同的一致性模型来满足不同的需求。这种思路其实和我们在深入HDFS提到基于环境去衡权是类似的。
一致性模型
下面我们先看看四个经典且常见的一致性模型:线性一致性、顺序一致性、因果一致性和最终一致性。
线性一致性(Linearizability)
线性一致性是 Herlihy 和 Wing 等于 1987 年在论文 “Axioms for Concurrent Objects” 中提出的,线性一致性也被称为原子一致性(Atomic Consistency)、强一致性(Strong Consistency)、立即一致性(Immediate Consistency)和外部一致性 (External Consistency)。
线性一致性是非常重要的一个一致性模型,在分布性锁、Leader 选举、唯一性约束等很多场景都可以看到它的身影。
这是最强的一致性模型。在这种模型下,系统表现得就好像只有一个数据副本,一旦新的值被写入或读取,所有后续的读都会看到写入的值,直到它被再次覆盖。在线性一致性模型中不论是数据的覆盖顺序还是读取顺序,都是按时间线从旧值向新值移动,而不会出现旧值反转的情况。
实现原理:要求所有的操作看起来是瞬时完成的,并且按照全局的时间顺序排列。
适用场景:对数据一致性要求极高的场景,如分布式事务处理。
优点:提供了最强的一致性保证,易于理解和推理。
缺点:实现代价高,性能开销大。
顺序一致性(Sequential Consistency)
顺序一致性是 Leslie Lamport 在 1979 年发表的论文 “How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Program” 中提出的,在论文中具体的定义如下:
A multiprocessor is said to be sequentially consistent if the result of any execution is the same as if the operations of all the processors were executed in some sequential order,and the operations of each individual processor appear in this sequence in the order specified by its program.
如果任何执行的结果与所有处理器的操作都以某种顺序执行的结果相同,并且每个单独的处理器的操作按照其程序顺序出现在该序列中,则称多处理器是顺序一致的
相对于线性一致性来说,顺序一致性在一致性方面有两点放松:
- 对于写操作,对没有因果关系的非并发写入操作,不要求严格按时间排序;
- 对于读操作,只要求所有的客户端观察到的顺序一致性,不要求写入后,所有的客户端都必须读取新值。
顺序一致性要求所有进程的操作都按照某种全局顺序执行,每个进程的操作在这个全局顺序中保持其原有的顺序。但并不要求操作的结果立即对所有进程可见,只需要保证所有进程看到的操作顺序是一致的。
实现原理:只要所有进程看到的操作顺序符合某种合法的全局顺序即可。
适用场景:多处理器系统中的共享内存模型。
优点:比线性一致性的限制稍弱,实现相对容易一些。
缺点:仍然对系统性能有一定影响。
因果一致性(Causal Consistency)
因果一致性是 Mustaque Ahamad, Gil Neiger, James E. Burns, Prince Kohli, Phillip W. Hutto 在 1991 年发表的论文 “Causal memory: definitions, implementation, and programming” 中提出的一种一致性强度低于顺序一致性的模型。
相对于顺序一致性来说,因果一致性在一致性方面有两点放松:
- 对于写操作,对没有因果关系的非并发写入操作,不仅不要求按时间排序,还不再要求节点 之间的写入顺序一致了;
- 对于读操作,由于对非并发写入顺序不再要求一致性,所以自然也无法要求多个客户端必须 观察到一样的顺序。
因果一致性模型保证如果一个操作 A 的执行结果会影响到另一个操作 B 的执行,那么所有进程都必须以 A 在 B 之前的顺序看到这两个操作。也就是说,存在因果关系的操作在所有节点上都以相同的因果顺序执行,但没有因果关系的操作可以以不同的顺序执行。
实现原理:通过识别和跟踪操作之间的因果关系来实现。
适用场景:对部分操作有顺序要求,但又希望提高系统性能和扩展性的场景,如分布式社交网络。
优点:在保证一定因果顺序的前提下,提高了系统的灵活性和性能。
缺点:因果关系的判断和处理较为复杂。
最终一致性(Eventual Consistency)
最终一致性是 Amazon 的 CTO Werner Vogels 在 2009 年发表的一篇论文 “Eventual Consistency” 里提出的,它是 Amazon 基于 Dynamo 等系统的实战经验所总结的一种很务实的实现,它不同于前面几种由大学计算机科学的教授提出的一致性模型,所以也没有非常学院派清晰的定义。
最终一致性是指在经过一段时间的延迟后,系统中的所有数据副本最终会达到一致的状态。在数据更新后的一段时间内,不同节点上的数据可能存在不一致,但随着时间的推移,数据会逐渐趋于一致。
“最终”是一个模糊的、不确定的概念,它是没有明确上限的,Vogels 提出这个不一致的时间窗口可能是由通信延迟、负载和复制次数造成的,但是最终所有进程的观点都一致,这个不一致的时间窗口可能是几秒也可能是几天。所以,最终一致性是一个一致性非常低的模型,但是它能非常高性能地实现,在一些业务量非常大,但是对一致性要求不高的场景,是非常推荐使用的。
实现原理:通常通过数据的异步复制和传播来实现。
适用场景:对数据一致性要求不那么严格,对可用性和性能要求较高的场景,如缓存系统。
优点:具有高可用性和良好的性能扩展性。
缺点:可能会在一段时间内存在数据不一致的情况。
经典一致性算法
2PC(Two-Phase Commit,两阶段提交)
2PC协议解决阻塞、数据不一致问题和单点问题。
原理:
- 准备阶段:协调者向所有参与者发送事务请求,参与者执行事务操作但不提交,记录日志并向协调者反馈执行结果。
- 提交阶段:若所有参与者都反馈成功,协调者发送提交指令,参与者正式提交事务;否则,协调者发送回滚指令,参与者回滚事务。
- 应用场景:对一致性要求较高,且能容忍一定性能开销的事务处理场景。
优点:原理简单、实现方便。能够保证事务的原子性,在大多数情况下可以有效地保证数据一致性。
缺点:单点故障问题,协调者故障可能导致整个事务阻塞;没有容错机制,参与者故障可能导致数据不一致;同步阻塞问题,会影响系统性能和并发度。
3PC(Three-Phase Commit,三阶段提交)
3PC协议解决2PC的阻塞,但还是可能造成数据不一致。
原理:
- 询问阶段:协调者询问参与者是否可以执行事务,参与者根据自身情况回复是否可以。
- 准备阶段:若询问阶段所有参与者都回复可以,协调者发送预提交请求,参与者执行事务操作并记录日志,向协调者反馈结果。
- 提交阶段:根据准备阶段的反馈,协调者决定提交或回滚事务并通知参与者执行。
应用场景:类似 2PC,但对系统可用性要求更高的场景。
优点:能够减小参与者阻塞范围,并能够在出现单点故障后继续达成一致。
缺点:实现相对复杂,性能开销较大;引入预提交阶段,在这个阶段如果出现网络分区,协调者将无法与参与者正常通信,参与者依然会进行事务提交,造成数据不一致。
Paxos算法
Paxos是基于消息传递的一致性算法。
其实,2PC和3PC都是分布式一致性算法的“残次”版本。Google Chubby的作者迈克·伯罗斯(Mike Burrows)说过,这个世界上只有一种一致性算法,那就是Paxos,其他算法都是残次品。
原理:通过多个节点之间的消息交互,选举出一个主节点来决定提案的通过与否,保证在多个节点中就某个值达成一致。主要角色有提议者、接受者、学习者,提议者提出提案,接受者决定是否接受提案,学习者学习被选定的提案。
应用场景:常用于分布式系统中的配置管理、分布式数据库的一致性维护等,如ZooKeeper中就使用了Paxos的变种算法。
优点:理论上能够保证在任何分布式环境下的一致性,具有很强的容错性和可靠性。
缺点:算法复杂,理解和实现难度大;性能方面,在大规模集群中可能存在较多的消息交互,影响效率。
Raft算法
Raft协议解决Paxos的实现难度。在Raft实现上,其将角色简化为Follower、Candidate、Leader这3种,角色本身代表状态,角色之间进行状态转移是一件非常自由的事情。Raft虽然有角色之分,但是采用的是全民参与选举的模式。在Paxos里,更像是议员参政模式。
原理:将节点分为领导者、跟随者和候选人三种角色。通过选举产生领导者,领导者负责接收客户端请求并向跟随者同步数据,保证数据的一致性。选举过程中,节点通过投票来选出任期内的领导者。
应用场景:广泛应用于分布式存储系统、分布式数据库等,如etcd就采用了Raft算法来保证数据一致性。
优点:相比Paxos算法更易于理解和实现,在性能和可用性方面表现较好,能够快速进行领导者选举和数据同步。
缺点:在极端网络环境下,如网络分区时间过长时,可能会出现数据不一致的情况。
ISR(In-Sync Replicas,同步副本)
ISR机制解决了容错的2N+1成本问题。
原理:Kafka中的每个分区都有一个领导者副本和多个跟随者副本,ISR 是与领导者副本保持同步的跟随者副本集合。生产者发送消息到领导者副本,领导者副本将消息写入本地日志后,会等待ISR中的所有副本都复制完消息后才向生产者确认消息发送成功,保证消息的一致性。
应用场景:适用于分布式消息队列系统,如Kafka在处理大规模消息的生产和消费时,并没有使用Zab或Paxos协议的多数投票机制来保证主备数据的一致性,而是通过ISR机制来保证数据一致性,并确保消息的可靠传递。
优点:能够在保证数据一致性的同时,提供较高的吞吐量和性能,支持大规模的消息处理。
缺点:ISR的管理和维护相对复杂,当ISR中的副本数量较少时,可能会影响数据的可靠性和一致性。
实用拜占庭容错(Practical Byzantine Fault Tolerance,PBFT)
实用拜占庭容错协议解决节点不可信问题(拜占庭将军问题)。Lamport证明在存在消息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。因此,在研究拜占庭将军问题的时候,要先假定信道是没有问题的,然后去做一致性和容错性的相关研究。
原理:在分布式系统中,节点之间通过消息传递进行通信,PBFT通过节点之间的消息交互和验证,在存在恶意节点(拜占庭节点)的情况下,仍然能够保证系统的一致性和正确性。算法通过视图切换、预准备、准备、提交等多个阶段来达成共识。
应用场景:对安全性要求极高的分布式系统,尤其是存在恶意攻击风险的环境。如区块链、金融交易系统等。
优点:能够容忍一定数量的拜占庭故障节点,保证系统的安全性和一致性,在联盟链等场景中有较好的应用。
缺点:通信复杂度高,性能开销大,随着节点数量增加,消息数量呈指数级增长,会影响系统的性能和扩展性。
总结
本文梳理了一致性问题来源和解决模型,以及经典常用的一致性算法,感兴趣的小伙伴可以深入了解不同算法的具体落地应用。后续在深入大数据相关技术技术框架时,遇到相关算法,我们再深入看看。