飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

分布式系统(三)——分布式事务

时间:2022-01-03  作者:MiaoMiaoGarden  

事务:用户的每个命令操作作为一个整体,不会因为失败而被分割,也不会被其他活动看到中间状态。事务处理系统要求程序员对这些读、写操作标明起始和结束,这样才能知道事务的起始和结束。事务处理系统保证在事务的开始和结束之间的行为是可预期的。
分布式事务的考量出现在这样的场景下:数据被分片存储到许多不同的服务器上。一个操作可能要求从多个服务器上修改或者读取数据,分布式系统要尝试向用户隐藏将数据分割在多个服务器上带来的复杂度。
数据库对于正确性有一个概念称为ACID:

  • Atomic:原子性。在一个事务里,要么所有的写数据全都完成,要么所有的写数据全都没有完成。
  • Consistent:一致性。
  • Isolate:隔离性。事务不能看到彼此的中间状态,只能看到完成的或者未完成的事务结果。通常来说,隔离性意味着可序列化。
  • Durable:持久化。事务提交后,数据库向用户反馈事务执行完毕后,数据库中的修改是持久的,不会因为一些错误而被擦除。

事务可能会在执行过程中Abort(例如除0错误、访问一个不存在的记录、死锁等),此时事务可能已经修改了数据库中的部分记录,我们需要能够回退这些事务,撤回已进行的修改。
分布式事务由两部分组成:并发控制(concurrency control)和原子提交(atomic commit)。

并发控制

在并发控制中,主要有两种策略:悲观并发控制和乐观并发控制。使用哪种策略取决于环境中冲突发生的频率。
悲观并发控制:数据库使用锁来保护数据,事务在使用任何锁之前,需要获得数据的锁,如果一些其他的事务已经在使用这里的数据,锁会被它们持有。在悲观系统中,如果有锁冲突,就会造成延时等待,为正确性而牺牲性能。
乐观并发控制:不用担心其他事务是否正在读写你要使用的数据,而是自顾自地继续执行。通常来说这些执行会有一些临时区域,只有在事务最后的时候才会检查是不是有其他事务干扰了你。这样做的优点是不用承担锁带来的性能损耗,缺点是如果有其他事务在同一时间修改了你关心的数据并造成冲突,那么必须Abort当前事务并重试。
悲观并发控制涉及的基本就是锁机制。这里的锁是两阶段锁(Two-Phase Locking)。

两阶段锁

当事务需要使用一些数据记录的时候,

  1. 第一阶段获取锁(expanding phase):在使用任何数据之前,在执行任何数据的读写之前,先获取锁;
  2. 第二阶段持有锁(constracting phase):事务必须持有任何已经获得的锁,知道事务提交或者abort,不允许在事务的中间过程释放锁,只能不断累积持有的锁,直到事务完成。
    两阶段锁的正确性证明:https://域名/p/59535337

两阶段提交

数据库的原子性是指事务的每一个部分都执行,或者任何一个部分都不执行。常用来达到原子性的方法是使用原子提交协议(Atomic Commit Protocols)。对于分布式系统,通常来说,原子提交协议的工作是帮助计算机决定是否能执行这个操作,是否执行了某个操作,或者出错了的时候所有计算机都要获得消息并且不再执行(回退)自己的操作。两阶段提交是一种原子提交协议,不仅被分布式数据库使用,也被各种分布式系统使用。

  1. 第一阶段投票阶段(prepare phase):事务协调者节点给每个参与者节点发送prepare消息,每个参与者要么直接返回失败,要么在本地执行事务,写本地的redo和undo日志,但不提交。
  • 协调者节点向所有的参与者节点发送待执行的操作,询问是否可以执行提交操作,并等待响应。
  • 参与者节点检查事务权限,写入undo和redo日志,再执行事务操作。
  • 参与者节点响应协调者节点的询问,如果执行成功,就返回同意,不成功就abort。
  1. 第二阶段提交阶段(commit phase):
  • 如果协调者收到参与者的abort消息或者超时,那么直接给每个参与者发送回滚消息;如果所有参与者都同意提交,发送commit消息给所有参与者。在发送abort/commit消息之前,持久化事务。
  • 参与者根据协调者指令执行回滚或提交操作,最后再释放事务处理过程中所有的锁。并反馈给协调者。
    tips:两阶段提交的升级版是三阶段提交,待补充。

故障恢复

【在各种故障场景下考虑两阶段提交协议对原子性的保障】
对于协调者:

  1. 发送commit消息之前崩溃:没有一个参与者会commit事务,可以直接abort事务。
  2. 发送完一个或者多个commit消息之后崩溃:协调者不可以忘记相关的事务,因为可能已经有参与者将事务在本地commit了,为此,协调者在发送commit消息之前,必须将事务的信息写入到自己的log,并持久化存储到磁盘中。

对于参与者:

  1. 参与者回复prepare之前故障:协调者abort事务。
  2. 参与者回复prepare之后,收到commit/abort之前故障:故障导致内存中有关事务、锁等的信息全部丢失,但由于是先写日志再执行操作,日志可以持久化在磁盘上持久化存储,故障重启就可以根据日志和协调者的commit/abort消息恢复信息。(tips:由于参与者必须将日志写道磁盘后才能执行以及回复协调者的prepare信息,因此两阶段提交协议的速度并不快。)
  3. 参与者处理完commit消息之后崩溃:事务已经执行完毕,不会出现错误。

两阶段提交的效率很低,因为它要求事务涉及的所有参与者都能正常工作。因此,两阶段提交虽然具备故障下的正确性,但不具备可用性。可以结合raft协议,对每个参与者节点构建一个raft集群,结合两阶段提交和raft协议的思想来同时获得高可用和原子提交。

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。