在分布式系统中,为了保证数据的高可用,通常,我们会将数据保留多个副本(replica),这些副本会放置在不同的物理的机器上。为了对用户提供正确的增\删\改\查等语义,我们需要保证这些放置在不同物理机器上的副本是一致的。
为了解决这种分布式一致性问题,前人在性能和数据一致性的反反复复权衡过程中总结了许多典型的协议和算法。其中比较著名的有二阶提交协议(Two Phase Commitment Protocol)、三阶提交协议(Three Phase Commitment Protocol)和Paxos算法。
事务处理几乎在每一个信息系统中都会涉及,它存在的意义是为了保证系统中所有的数据都是符合期望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性(Consistency)。
按照数据库的经典理论,要达成这个目标,需要三方面共同努力来保障。
以上四种属性即事务的“ACID”特性,但笔者对这种说法其实不是太认同,因为这四种特性并不正交,A、I、D 是手段,C 是目的,前者是因,后者是果,弄到一块去完全是为了拼凑个单词缩写。
事务的概念虽然最初起源于数据库系统,但今天已经有所延伸,而不再局限于数据库本身了,所有需要保证数据一致性的应用场景,包括但不限于数据库、事务内存、缓存、消息队列、分布式存储,等等,都有可能会用到事务,后文里笔者会使用“数据源”来泛指所有这些场景中提供与存储数据的逻辑设备,但是上述场景所说的事务和一致性含义可能并不完全一致,说明如下。
外部一致性问题通常很难再使用 A、I、D 来解决,因为这样需要付出很大乃至不切实际的代价;但是外部一致性又是分布式系统中必然会遇到且必须要解决的问题,为此我们要转变观念,将一致性从“是或否”的二元属性转变为可以按不同强度分开讨论的多元属性,在确保代价可承受的前提下获得强度尽可能高的一致性保障,也正因如此,事务处理才从一个具体操作上的“编程问题”上升成一个需要全局权衡的“架构问题”。
人们在探索这些解决方案的过程中,产生了许多新的思路和概念,有一些概念看上去并不那么直观,在本章里,笔者会通过同一个场景事例讲解如何在不同的事务方案中处理来贯穿、理顺这些概念。
其实事务根据不同的场景会划分成很多类型,比如 本地事务、全局事务、共享事务、分布式事务,我们今天来讲的主要是分布式事务。
对于其他事务我们就不仔细提了。
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
分布式事务
是指会涉及到操作多个数据库的事务。
其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。分布式事务处理的关键是必须有一种方法可以知道事务在任何地方所做的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或全部回滚)
在分布式系统中,各个节点之间在物理上相互独立,通过网络进行沟通和协调。
由于存在事务机制,可以保证每个独立节点上的数据操作可以满足ACID。但是,相互独立的节点之间无法准确的知道其他节点中的事务执行情况。所以从理论上讲,两台机器理论上无法达到一致的状态。
如果想让分布式部署的多台机器中的数据保持一致性,那么就要保证在所有节点的数据写操作,要么全部都执行,要么全部的都不执行。但是,一台机器在执行本地事务的时候无法知道其他机器中的本地事务的执行结果。所以他也就不知道本次事务到底应该commit还是
roolback。所以,常规的解决办法就是引入一个“协调者”的组件来统一调度所有分布式节点的执行。
二阶提交协议和三阶提交协议就是最早提出解决分布式一致性的方案。
二阶段提交(Two-phaseCommit)是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性当一个事务跨越多个节点时,为了保持事务的ACID特性,引入一个作为协调者的组件。
二阶段提交的算法思路可以概括为:
- 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
所谓的两个阶段是指:
事务协调者给每个参与者发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。
? 投票阶段的三步骤:
1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
2)参与者节点检查事务权限,执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
【注:为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志(凭证),出问题容易死无对证;】
3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)
接下来分两种情况分别讨论提交阶段的过程。
情况 1
当协调者节点从所有参与者节点获得的响应消息都为”同意”时,提交过程如下图所示:
协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”完成”消息。
4)协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。
情况 2
如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”回滚完成”消息。
4)协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
不管最后结果如何,第二阶段都会结束当前事务。
二阶段提交看起来确实能够提供原子性的操作,但是不幸的事,二阶段提交还是有几个缺点的:
1、同步阻塞问题。
执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。也就是说从投票阶段到提交阶段完成这段时间,资源是被锁住的。2、单点故障。
由于协调者的重要性,
一旦协调者发生故障。参与者会一直阻塞下去。
尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
【协调者发出Commmit消息之前宕机的情况】
(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)3、数据不一致。
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
4、二阶段无法解决的问题:------ 极限情况下,对某一事务的不确定性!【协调者发出Commmit消息之后宕机的情况】
协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
相同点
- 两个阶段:TiDB的两阶段提交同样遵循基本的2PC框架,包括“准备阶段”和“提交/回滚阶段”。
- 确保数据一致性:两者都是为了保证事务在分布式系统中的一致性,确保要么所有节点都提交事务,要么都不提交。
不同点
- 设计背景:传统的两阶段提交是为了通用的分布式事务设计,而TiDB的两阶段提交是专门为TiDB这种分布式数据库系统量身定制的。
- 实现细节:TiDB在实现两阶段提交时做了很多针对性的优化。例如,它可能在事务冲突检测、锁定资源等方面有独特的处理方式,以适应其分布式架构。
- 性能优化:TiDB的两阶段提交可能包含更多的性能优化措施,以减少跨网络通信的开销和提高事务处理的效率。这些优化可能包括数据局部性考虑、事务日志处理方式等。
- 故障处理:在故障恢复和事务持久性方面,TiDB可能有自己的机制,例如利用其自身的存储引擎特性来优化数据恢复过程。
- 可扩展性和灵活性:作为一个为大规模分布式环境设计的系统,TiDB的两阶段提交可能更注重于扩展性和灵活性,以适应大量节点和高并发的场景。
结论
虽然TiDB的两阶段提交在高层框架上与传统的两阶段提交相似,但它通过特定的优化和调整更适合于其分布式数据库的环境。这些差异体现了TiDB在保持一致性和提高性能方面的特定考量。在选择分布式数据库和事务协议时,重要的是要考虑系统的具体需求和运行环境。
相同点
- 基本框架:无论在哪种场景下讨论,两阶段提交的基本框架是相同的。
- 它都包含两个阶段:准备阶段和提交/回滚阶段。
- 一致性保证:两者都旨在确保分布式系统中事务的一致性。即要么所有节点都提交事务,要么都不提交。
可能的不同点
- 实现细节:不同系统中的两阶段提交可能在实现细节上有所差异。这些差异可能源于系统架构、性能要求、容错机制等方面的考量。
- 性能优化:在不同的系统中,可能会采取不同的优化措施来提高两阶段提交的效率,减少其对系统性能的影响。
- 故障处理和恢复:不同的系统可能会采用不同的策略来处理和恢复事务协议中的失败情况,这取决于系统的容错需求和可用资源。
- 应用场景:某些系统可能会根据其特定的应用场景对两阶段提交进行调>整,以更好地满足其需求。
结论
当我们在不同上下文中讨论两阶段提交时,基本的协议框架和目标是一致的,但实现的具体细节、性能优化、故障处理等方面可能会根据具体的系统和应用场景有所不同。无论在哪种情况下,两阶段提交的主要目的都是确保在分布式系统中处理事务时的数据一致性。
参与者
参与者为 TiKV,在 TiDB 的两阶段提交过程中,参与者通常是指存储数据的节点。TiDB采用了 TiKV 作为其存储层,所以参与者在这里指的是TiKV 的节点。每个 TiKV 节点负责存储数据的一部分,并在两阶段提交过程中执行相关的操作,如锁定资源、写入数据、回滚或提交事务等。
协调者
协调者在 TiDB 的两阶段提交中通常是指负责协调事务的组件,这通常是 TiDB 服务器本身。TiDB 服务器接收来自客户端的事务请求,然后它负责在两阶段提交过程中与多个 TiKV 节点通信,协调整个事务的提交或回滚。
两阶段提交过程概述
- 第一阶段(预提交):TiDB服务器(作为协调者)向TiKV节点(参与者)发送预提交请求。TiKV节点在收到请求后执行事务操作,并将结果返回给TiDB服务器。
- 第二阶段(提交或回滚):根据第一阶段的结果,TiDB服务器会决定是提交事务还是回滚事务。如果所有TiKV节点都成功预提交,TiDB服务器会发送提交请求;如果任何一个TiKV节点预提交失败,TiDB服务器会发送回滚请求。
在TiDB中,由于其分布式的特性,两阶段提交是一个关键的机制,用以确保数据的一致性和事务的原子性。该过程涉及到复杂的协调和通信,以确保即使在分布式环境中也能有效地处理事务。
ScyllaDB,一种高性能的分布式NoSQL数据库,实现了分布式一致性,主要通过采用Apache Cassandra的一致性模型。这个模型是基于Amazon的DynamoDB设计的。ScyllaDB的分布式一致性主要通过以下几个方面来实现:
- 数据复制(Replication):
ScyllaDB在多个节点上复制数据,以确保高可用性和容错性。
使用的是一种叫做“虚拟节点”的技术,它允许数据自动均匀分布在集群的所有节点上。- 一致性级别(Consistency Levels):
ScyllaDB允许客户端在读写操作中选择不同的一致性级别,例如ONE(只需要一个节点响应)、QUORUM(大多数节点)、ALL(所有节点)等。
不同的一致性级别可以平衡读写延迟、数据一致性和系统的容错能力。- 读写路径:
写操作:一个写请求首先被发送到协调节点,然后根据一致性级别,协调节点负责将写操作复制到其他节点。
读操作:读请求也是先到协调节点,然后根据选择的一致性级别,协调节点从一个或多个节点获取数据。- 故障恢复和一致性保证:
ScyllaDB使用“Hinted Handoff”和“Read Repair”机制来处理节点故障和数据不一致的情况。
“Hinted Handoff”允许暂时不可达的节点稍后接收更新。
“Read Repair”在读操作过程中检测数据不一致,并进行修复。- Gossip协议:
ScyllaDB使用Gossip协议来维护节点之间的信息同步,包括节点的状态、数据分布和集群的拓扑结构。- 分布式事务支持:
ScyllaDB支持轻量级事务,使用乐观锁和基于时间戳的冲突解决机制。
通过这些机制,ScyllaDB能够在不同节点之间有效地复制和同步数据,确保即使在部分节点不可用的情况下也能维持数据的一致性和系统的高可用性。用户可以根据自己的需求和场景选择合适的一致性级别,以在性能和一致性之间做出平衡。
在分布式系统中,"提交"通常指的是确认并应用一组操作或事务,确保这些操作在所有相关节点上得到一致的执行。提交的必要性和数据同步的概念虽然相关,但它们在实际应用中有所区别。以下是这两个概念的关键方面:
提交的情况
- 分布式事务完成:在分布式数据库或系统中执行事务时,当所有参与的节点准备就绪,且满足一致性条件时,需要进行提交操作。这确保了事务的原子性和一致性。
- 一致性保证:在任何需要保持数据一致性的操作中,如多个节点间的数据更新,提交是必要的步骤。这确保所有节点上的数据达到一致状态。
- 系统状态变更:在某些系统中,如配置更改或重要状态更新,在所有相关节点上达成一致后需要进行提交。
数据同步
数据同步涉及保持不同系统或组件间数据的一致性。
它可能涉及以下情况:
- 实时或定期更新:数据同步可能是实时的(如数据库复制)或按计划进行的(如定时备份)。
- 多源一致性:在多个数据源之间同步数据时,数据同步确保所有源保持一致。
- 故障恢复和备份:数据同步也是故障恢复和备份策略的一部分,确保数据的持久性和可用性。
相关性和区别
相关性:提交通常是数据同步过程的一部分。例如,在分布式数据库的两阶段提交协议中,提交是同步数据到所有节点的最后步骤。
区别:提交是一个决策点,表示事务或操作的完成,而数据同步是一个持>续的过程,涉及数据在节点间的持续一致性维护。
在分布式系统的设计和操作中,理解这两个概念及其相互作用是非常重要的,以确保系统的稳定性和数据的一致性。
三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
三阶段提交示意图
与两阶段提交不同的是,三阶段提交有两个改动点。
1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个**准备阶段,**保证了在最后提交阶段之前各参与节点状态的一致。
也就是说,除了引入超时机制之外,3PC把2PC的投票阶段再次一分为二,这样三阶段提交就有CanCommit
、PreCommit
、DoCommit
三个阶段。
为什么要把投票阶段一分为二?
假设有1个协调者,9个参与者。其中有一个参与者不具备执行该事务的能力。协调者发出prepare消息之后,其余参与者都将资源锁住,执行事务,写入undo和redo日志。
协调者收到相应之后,发现有一个参与者不能参与。所以,又出一个roolback消息。其余8个参与者,又对消息进行回滚。这样子,是不是做了很多无用功?
所以,引入 can-Commit 阶段,主要是为了在预执行之前,保证所有参与者都具备可执行条件,从而减少资源浪费。 减少无用功。
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
1.事务询问
协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈
参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段
本阶段协调者会根据第一阶段的询盘结果采取相应操作,询盘结果主要有两种:
情况1- 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行:
1.发送预提交请求
协调者向参与者发送PreCommit请求,并进入Prepared阶段。
2.事务预提交
参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
3.响应反馈
如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
情况2- 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。具体步骤如下:
1.发送中断请求
协调者向所有参与者发送abort请求。
2.中断事务
参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
doCommit 阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
情况1-执行提交
针对第一种情况,协调者向各个参与者发起事务提交请求,具体步骤如下:
- 协调者向所有参与者发送事务commit通知
- 所有参与者在收到通知之后执行commit操作,并释放占有的资源
- 参与者向协调者反馈事务提交结果
情况2-中断事务
协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。具体步骤如下:
1.发送中断请求协调者向所有参与者发送事务rollback通知。
2.事务回滚所有参与者在收到通知之后执行rollback操作,并释放占有的资源。
3.反馈结果参与者向协调者反馈事务提交结果。
4.中断事务协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
三段提交协议(3PC)相对于两段提交协议(2PC)的主要改进在于它增加了一个额外的阶段,即“预提交”阶段,并通过这个阶段来减少阻塞和改善在某些失败情况下的处理。下面是两者的主要差异和3PC的改进之处:
尽管3PC相对于2PC有所改进,但它仍然不能完全解决所有分布式事务问题,特别是在分布式系统中面对网络分区和同时多点故障时。此外,3PC相比2PC更加复杂,可能会引入额外的开销。在实际应用中,选择哪种协议需要根据系统的特定需求和特点来决定。
三阶段提交协议(3PC)并不像两阶段提交协议(2PC)那样在商业和开源中间件或框架中广泛使用。3PC主要作为2PC的改进,用于解决在分布式系统中的一些特定问题,比如降低系统在协调者失败时的阻塞风险。然而,由于它的复杂性和在某些场景下的性能开销,3PC并没有得到广泛的实际应用。
尽管不广泛,3PC仍然可能在一些特定场景中被采用,尤其是在那些对于系统阻塞和故障恢复有更高要求的环境。例如:
- 高可用性系统:在需要高度可用性且不能容忍长时间锁定资源的系统中,3PC可能会被考虑。
- 分布式数据库:一些分布式数据库系统可能会选择使用3PC来提高在网络分区或协调者故障时的容错性。
在现实中,由于3PC仍然不能完全解决网络分区问题,并且带来了额外的复杂性和开销,许多分布式系统更倾向于使用其他技术来处理一致性和容错问题,如:
3PC 的主要作用是在分布式事务处理中提供一种比 2PC 更安全的机制,减少在协调者失败时的阻塞风险,同时试图在一定程度上提高系统在面对故障时的容错能力。
总的来说,尽管3PC在理论上是一个有吸引力的概念,但由于实际应用中的挑战,它并没有在中间件或框架中得到广泛的采用,特别是当与其他更成熟的技术相比较时。在选择分布式事务协议时,通常需要根据系统的具体需求和特性进行综合考虑。
分布式一致性之两阶段提交协议、三阶提交协议
凤凰架构
chatgpt4