分布式原子提交协议2PC
简介
随着数据以惊人的速度增长,原有的单体非分片数据库已无法处理大量的、且非结构化的数据。基于这一原因,NOSQL
数据库逐步在一企业应用中流行起来。大部分NOSQL
数据库数据存储是分布在许多机器上,这种技术也称为“分片
”。
当数据需要原子地存储在多个集群节点上时,客户端读写数据需要在多个节点上保持数据的一致,这个过程也称为分布式事务。在之前我们的文章详细介绍过事务的原理以及分布式事务DTP模型。其中我们还以银行转账为例,介绍了基于XA规范的二阶段提交协议,也即2PC(Two Phase Commit)。
两阶段提交的本质是它分两个阶段执行更新:
- 首先,准备提交请求,询问每个节点是是否能够承诺执行更新
- 其次,提交执行,根据每个节点反馈确认执行或者回滚
为什么需要2PC
简单的一句话来形容,就是容错。在分布式系统下,需要应对各种挑战,比如硬件和网络故障。出现这些故障,事务要么在故障解决之后继续执行下去,要么回滚。
2PC工作原理
现在,我们现在来看看 2-Phase 协议的工作原理。我们引入了一个名为 的事务协调者(Transaction Coordinator
)。该实体协调事务的提交部分。管理单个事务的其他服务器称为参与者(Participants
)。
在我们的示例中,我们有两个事务Txn Credit
& Txn Debit
处理银行借记交易期间账户余额变动。Txn Credit
runs on Shard A
& Txn Debit
runs onShard B
分别。客户端启动两个事务并将它们发送到两个分片。下图说明了这个过程。两个数据库服务器都开始执行事务。
稍后,客户端向事务协调者写入数据,事务协调者会把请求拆分成提交请求和提交执行这两个阶段。
-
提交请求阶段
RequestCommit
消息被发送到所有参与者服务器,所有参与者服务器要判断最近是否可以执行这个请求,如果不行的话,它会直接返回给协调者,说自己不能执行这个事务,也就是向服务器返回一条FAIL
消息,而如何它确定最近可以执行事务,那么,它会先把要进行的事务以预写日志的方式写下来,然后向服务器返回一条OK
消息。 -
提交执行阶段
Transaction Coordinator
等待所有服务器的响应。一旦收到响应,它将决定提交或中止事务。这成为提交的第二阶段。只有当每个服务器都回复一条OK
消息时,事务才会被提交。如果至少有一台服务器响应FAIL
消息,则事务将中止。
下图显示了每个服务器都回复一条OK
消息的情况。每个其他服务器都从协调器收到一个 Commit 并且事务成功。
在FAIL
消息的情况下,Transaction Coordinator
向所有参与者发送中止消息。结果,单个事务被参与者回滚。
上述过程保证了分布式事务的原子性。事务将在所有服务器上提交或在所有服务器上回滚。但是,它不会在中途处于不一致状态。不会出现一个帐户在不借记另一个帐户的情况下被记入贷方的情况,反之亦然。
通俗的讲,所有的参与者,一旦在提交请求阶段答应自己会执行事务,就不能再反悔了。如果参与者觉得自己不能执行对应的事务,就需要在提交请求阶段就拒绝掉。
比如,如果参与者是一个 MySQL 数据库,那么如果协调者发起的数据写入请求,可能会违背 MySQL 里某个表的字段的唯一性约束。这样 MySQL 数据库就应该在提交请求阶段告诉协调者,而不是等到要实际执行的时候才说。而协调者这个时候,就会在提交执行阶段,直接发送事务回滚的请求。这个时候,各个参与者写下的 undo logs 就会派上用场了,各个节点可以回滚刚才写入的数据,整个事务也就没有发生。
2PC故障处理
了解完2PC工作原理,我们再回头理解2PC容错处理。也就是说,在这个两阶段提交的过程中,如果出现了硬件和网络故障,会发生什么事情呢?
- 如果是参与者发生了硬件故障,或者参与者和协调者之间的网络出现了故障。这个时候的硬件或者网络故障,就意味着参与者没有办法知道协调者到底想要继续推进事务,还是想要回滚。在这种情况下,参与者在硬件故障解决之后,会一直等待协调者给出下一步指令。
- 如果协调者之前已经收到了参与者的答应执行事务的响应,那么协调者会一直尝试重新联系参与者。
- 如果参与者答应执行事务的响应还没有来得及给协调者,那么协调者在等待一段时间没有得到响应之后,会最终决定放弃整个事务,整个事务会回滚。
- 如果发生了硬件故障,一个参与者已经在自己的节点上完成了事务的执行。但是另外一个参与者,可能要过很长一段时间,在硬件和网络恢复之后,才会完成事务。
2PC缺点
欲戴其冠、比承其重,正因为两阶段提交协议故障处理机制,所以也带来一些缺点:
- **参与者依赖性:**缓慢的参与者会影响其他参与者的表现。总事务时间与最慢的服务器所花费的时间成正比。如果事务在单个服务器上失败,则必须在所有其他服务器上回滚。这可能会导致资源浪费。
- **延迟:**正如我们所见,事务协调器等待所有参与者服务器的响应。只有这样它才能继续提交的第二阶段。这会增加延迟,并且客户端可能会遇到执行缓慢的情况。因此,2PC 不是性能关键型应用程序的好选择。
- **事务协调器单点:**事务协调器有时会成为单点故障。事务协调器可能会在向所有参与者发送提交消息之前关闭。在这种情况下,所有在参与者上运行的交易都将进入阻塞状态。只有当协调器出现并发送提交信号时,他们才会提交。
小结
2PC本质是一个CP系统,优先考虑一致性,会牺牲可用性。也就是说,任何一个服务器节点出现问题,都会导致一次“单点故障”。正因为两阶段存在这种缺陷,为了提升整个系统的可用性,又提出了一个新的理论:3PC(三阶段提交协议)
三阶段提交其实优化两阶段提交中第一阶段提交请求。
原有第一阶段,每个参与者都要在不知道其他参与者是否可以执行事务情况下,先把完成事务和回滚事务所有动作都准备好。三阶段提交在两阶段提交的第一阶段与第二阶段之间插入了一个准备阶段,令原先在两阶段提交中,参与者在投票之后,由于协调者发生崩溃或错误,而导致参与者处于无法知晓是否提交或者中止的“不确定状态”所产生的可能相当长的延时的问题得以解决。