分布式系统学习小记9 —— 分布式事务

“Hi,这是小5自学算力大集群相关知识的第9篇笔记。这次我们将了解分布式事务的一些基本知识以及“正确性”、并发控制、原子提交等概念。”

摘要

“事务”(Transaction),是数据库或应用中的‌逻辑执行单元。通常指将并发控制和原子提交打包抽象的一种概念。分布式事务之所以出现,是因为对于拥有大量数据的人们来说,最终将数据分割或分片到许多不同服务器上非常频繁。

好吧,这一次要说的东西确实有点抽象。让我们不妨先抛开对于抽象概念的执着,先尝试看看它是用来做什么的以及如何运作的。


  • 关于正确性

数据库通常具有一个称为ACID的正确性概念。其中各个字母所代表的含义如下:

A:原子性

一个包含多个步骤的操作或交易,可能涉及对多个不同记录的写入,若发生故障,要么所有写入操作都应完成,要么全部不执行。就好比一个同学在写作业,他要么全部写完交上去,要么一个字都不写,也就是不能只写一半交上去;

C:一致性

通常意味着数据库会执行应用程序声明的某些不变量。类比的话就是作业内容要符合题目要求,比如数学题不能写成作文;

I:隔离性

指事务之间无法看到彼此的变更的中间状态,仅能看到完整的事务结果。即,并发事务互不干扰,避免脏读、幻读等问题‌。也指事务执行的可串行化(serializability):

如果一组事务并发执行,或多或少在同一时间进行,它们将产生一组结果。而实际执行过程可能蕴含大量并行性,必须确保其结果与单次顺序执行相同事务的结果一致。

查看一组并行处理的数据,它们在不同执行顺序下,产生的结果。如先执行操作A,再执行操作B;或先执行操作B,再执行操作A。虽然有可能产生不同的结果,但这些结果都是合法的。如果实际执行中未发现其他不同于上述推演出的所有结果,则表示正在使用的数据库提供可串行化执行。只要事务不使用相同的数据,就允许真正并行的执行。

D:持久性

在事务提交后,客户端或提交该事务的任何程序收到数据库的回复:确认已执行该事务。即事务对数据库的修改是持久的,这些修改将得以保留。


  • 关于并发控制

并发控制是用来提供串行化或隔离性的主要工具。其作用是隔离其他可能试图使用相同数据的并发事务。

一般存在两种并发控制策略:

1.悲观并发控制

通常指的是锁定机制。即在事务使用任何数据之前,它需要获取该数据的锁。若其他事务已占用该数据,锁将被保持,需等待以获取锁,等待另一事务完成。若存在锁定冲突,即他人持有锁,将会导致延迟。所以这是以性能换取正确性。

具体是通过二阶段锁定(two-phase locking)的方式实现。第一条规则是:在使用任何数据片段之前,无论是读取还是写入任何记录,都必须先获取锁;第二条规则:事务必须在提交或中止之后才能释放其所获取的任何锁。

比如,一个事务将会使X和Y数据。当首次使用X时,必须在允许使用前获取X上的锁(可能需要等待释放)。同样,当首次使用Y时,会获取另一个锁,即Y上的锁。在一个同时需要用到X与Y的操作流程中,需要在完成所有操作后,才能释放这两个锁。与此同时,另一个未能首先获得X锁的事务将在此等待,直到能够获取该锁之前,它不会对X进行任何操作。

关于这些规则需要注意的另一点是,它们很容易导致死锁。

2.乐观并发控制

无需顾虑其他事务是否可能同时进行数据的读写操作,只需直接执行计划中的读写操作,尽管通常是写入某种临时区域。只有在最后,才会去检查是否可能有其他事务实际上产生了干扰。

如果与此同时,有其事务在以冲突的方式修改同一数据,那么就必须中止该事务并重试。如果冲突较为罕见,那么乐观并发控制可能更快,因为它完全避免了锁开销。

*如果冲突非常频繁,可能会更倾向于使用悲观并发控制。因为如果冲突频繁发生,乐观方案将会因冲突而导致大量事务中止。


  • 关于原子提交

事务在执行过程中,可能已经修改了变量X,但突然间其中一个涉及的服务器发生了故障,如数据X和Y位于不同的机器上。我们需要能够在即使只有部分机器发生故障的情况下,恢复这些正是运行该事务的机器。

原子提交协议将协助计算机做出决定:要么它们都能完成各自的任务并实际执行,要么出现了问题,它们将一致同意,没有任何一台计算机能够完成其在整个任务中的部分。

这就需要用到两阶段提交协议 (two-phase commit)

系统中的每条消息都附有其所适用的交易的唯一交易ID标签。这些ID由事务协调器在事务启动时选定,称为事务标识符或TID。

此外,系统内还存在一个驱动事务的计算机,称为事务协调器。其他服务器则被称为参与者。

当交易协调器到达交易操作序列的末尾并希望提交它,以便能够释放所有那些锁,并使交易结果对外界可见时。事务协调器必须确保所有不同的参与者都能够实际执行其事务的一部分。特别是在事务中,如果有任何的写入操作,需要确保执行这些写入的参与者实际上仍然具备执行写入操作的能力。事务协调器会向所有参与者发送准备消息,当参与者们接收到prepare消息时,它们会审视自身状态,以确定是否真正具备完成该事务的能力,它们会以“是”或“否”来回应。事务协调器等待每位参与者发出的同意或否决票。如果它们都表示同意,那么该事务即可提交。事务协调器将给每位参与者发送出提交消息。随后,参与者通常会回复一个确认信息,被称为确认(Acknowledgement)。

但在上述流程中,如果其中任何一个参与者表示拒绝,将无法完成操作,则必须中止操作。交易协调器将不会提交,它将发送一轮中止消息,并请求参与者进行撤销。

在每个参与者内部,都存在一张与该参与者存储的数据相关联的锁表。为了遵守锁定规则,参与者会在观察到提交(commit)或中止(abort)时解锁;每个参与者在执行事务的一部分时,都会锁定其读取的任何数据。

两阶段提交方案的问题

1.会对性能造成影响:

1)多轮消息传递,为了完成涉及多个参与者的交互,需要进行大量的闲聊;

2)磁盘写入,参与者在准备阶段与发送肯定响应之间,不仅需要将数据写入磁盘,还必须等待该磁盘写操作完成;

3)在收到最后一个确认“是”之后,事务协调器必须首先将其写入目志,确保数据已安全存储在磁盘上,然后才能发送提交消息。

2.一旦出现任何差错,如消息丢失或系统崩溃,若运气稍差,参与者便不得不长时间持有锁等待。


故障恢复时可能会面临几种情况:

1.某参与者可能在发送确认消息之前就已经崩溃了

事务协调器不可能已经提交或即将提交,因为它必须等待所有参与者的肯定答复。所以,在崩溃的参与者重启后,当事务协调器参与者发送准备消息,而该参与者对该事务一无所知时,参与者会回复中止(abort)。

2.某参与者可能在发送了肯定回复之后崩溃了

1)在接收到事务协调器的提交消息之前,它崩溃了。崩溃的参与者会在回复准备请求之前,创建事务状态,即一种中间事务状态,记录所有已做的更改,这些更改在发生中止时可能需要撤销,同时还要记录该事务持有的所有锁。必须将其持久化到磁盘上(可能会对性能产生影响)。在接收到提交指令时,它将通过查阅其日志了解到如何实际完成对应操作。

2)接收到提交消息后崩溃。事务在接收到提交消息之前,已将其所需的修改永久保存在数据库中,并写入磁盘,所以无需采取任何措施。

3.当事务协调器发生故障

无论是参与者之一可能已经提交,还是事务协调器可能已向客户端回复,系统都不能让该事务消失。事务协调器必须在重启时准备重新发送提交消息,确保参与方均知晓事务已提交。事务协调器根据这些“是与否”投票做出提交或中止的决定后,在发送任何提交消息之前,必须首先将有关该事务的信息写入其日志中的持久存储。事务协调器在接收到完整的“是”或“否”响应集合后,会将结果及事务ID记录至其磁盘日志中,仅在此之后才开始向所有参与者发送提交消息或中止消息。

除故障情况外,还可能存在网络中消息丢失的情况。

假设事务协调器发送了准备消息,但尚未从参与者那里收到一些“是”或“否”的回复。此时可以发送一组新的准备消息,但仍然在一定时间内,事务协调器未从参与者处收到“是”或“否”的响应,那么它可以单方面决定中止当前事务。

相对,若参与者正等待准备阶段的消息,此时网络出现问题或事务协调器暂时宕机。参与者始终有权中止事务,从而释放其他事务可能正在等待的锁。但参与者在回复了prepare消息yes之后且接收到commit消息之前,若超时等待commit,则不允许中止。

事务协调器可以清除其日志中关于事务的信息吗?

如果系统成功接收到来自所有参与者的完整确认信息,那么它就能确定所有参与者均已知晓该交易的提交或报告状态,即所有参与者都已了解该交易的最终结果,并完成了各自在其中的任务。此时,当交易协调器收到确认信息后,便可以清除所有相关信息,即该交易的所有内存记录。

同样,参与者一旦收到提交或中止消息,并在事务中完成了自己的部分,使更新永久化并释放了锁,在那一刻,参与者也可以完全清除该事务,在他们将确认信息返回给事务协调器之后。


  • 与Raft对比

Raft的核心在于,即使参与其中的部分服务器发生崩溃或无法访问,系统仍能正常运行。是因为所有服务器都在执行相同的操作,它们执行的是相同任务,因此无需所有节点都参与,只需要多数即可。

两阶段提交中,参与者并非完全执行相同的操作。参与者各自负责交易的不同部分,如服务器A可能在增加记录X,而服务器B可能在减少记录Y。所以需要等待每一位参与者完成他们各自的任务。若出现任何问题,我们可能需要等待直到修复完成。因此并不具备高可用性。

是否能构建一种综合系统,它通过复制实现Raft协议的高可用性一同时又具备两阶段提交方案的能力,促使各方各自完成交易中的一部分?

一个示例:事务协调器由一个拥有三个服务器的复制服务组成,在这三个服务器上运行一个Raft协议,其中一位将被选为领导者。它们会有一个日志帮助其进行复制。只需确保其中多数处于运行状态,以便事务协调器能够执行其工作。而每个参与者也将是一个集群,即一个Raft复制的集群。


以上内容来自对 MIT 《分布式系统 6.824》的学习笔记,在此特别声明。