zl程序教程

您现在的位置是:首页 >  工具

当前栏目

《数据密集型应用系统设计》读书笔记

应用系统数据 设计 读书笔记
2023-09-14 09:11:43 时间

一、数据系统

  • 可靠性、可拓展性、可维护性。

二、数据模型与查询语言

  • 关系模型
  • 文档模型
  • 图模型
  • NoSql:Redis
  • 一对一,一对多,多对多。

三、数据存储与检索

  • 哈希索引
    键-值(key-value)数据的索引。
  • B-trees
    B-tree 将数据库分解成固定大小的块或页,传统上大小为4kb。
    页是内部读/写的最小单元。
    每个页面都可以使用使用地址或者位置进行标识,这样可以让一个页面引用另一个页面,可以使用这些页面来构造一个树状页面。
  • OLTP,OLAP。
    OLTP:在线事务处理。涉及到事务。
    OLAP:在线分析处理。主要是分析。
  • 将数据导入数据仓库的过程称为提取-转换-加载(Extract-Transform_Load,ETL)
  • 列式存储:如果每个列存储在一个单独的文件中,查询只需要读取和解析使用的那些列。

四、数据编码与演化

  • 数据流模式:数据可以通过多种方式从一个进程流向另一个进程。
    通过数据库、通过服务调用、通过异步消息调用。

基于服务的数据流:REST和RPC.

  • REST不是一种协议,而是一个基于HTTP原则的设计理念。强调简单的数据格式,使用URL来标识资源。根据REST原则所设计的API称为 RESTful。
  • 远程过程调用(RPC).RPC模型试图使向远程网络服务发出请求看起来与在同一进程中调用函数相同。
    RPC注意点,失败重试、超时、幂等性、网络延迟、序列化。
  • 通过异步消息调用.使用消息代理,节点之间通过互相发送消息进行通信。

分布式数据系统

第五章 数据复制

复制与分区

将数据分布在多节点时有两种常见的方式:

复制

在多个节点上保存相同数据的副本,每个副本具体的存储位置可能不尽相同。复制方法可以提供冗余:如果某些节点发生不可用,则可以通过其他节点继续提供数据访问服务。复制也可以帮助提高系统性能。

分区

将一个大块头的数据库拆分成多个较小的子集即分区,不同的分区分配给不同的节点(也称为分片)。

主从复制、多主节点复制和无主节点复制。

主节点与从节点

每个保存数据库完整数据集的节点称之为副本。当有了多副本,不可避免地会引入一个问题:如何确保所有副本之间的数据是一致的?
对于每一笔数据写入,所有副本都需要随之更新;否则,某些副本将出现不一致。
最常见的解决方案是基于主节点的复制(也称为主动/被动,或主从复制),主从复制的工作原理如下:

1. 指定某一个副本为主副本(或称为主节点)。当客户写数据库时,必须将写请求首先发送给主副本,主副本首先将新数据写入本地存储。
2. 其他副本则全部称为从副本(或称为从节点)注1。主副本把新数据写入本地存储后,然后将数据更改作为复制的日志或更改流发送给所有从副本。每个从副本获得更改日志之后将其应用到本地,且严格保持与主副本相同的写入顺序。
3. 客户端从数据库中读数据时,可以在主副本或者从副本上执行查询。再次强调,只有主副本才可以接受写请求;从客户端的角度来看,从副本都是只读的。

同步复制与异步复制。

处理节点失效。

  • 从节点失效:追赶式恢复。从节点的本地磁盘上都保存了副本收到的数据变更日志。如果从节点发生崩溃,然后顺利重启,或者主从节点之间的网络发生暂时中断(闪断),则恢复比较容易,根据副本的复制日志,从节点可以知道在发生故障之前所处理的最后一笔事务,然后连接到主节点,并请求自那笔事务之后中断期间内所有的数据变更。在收到这些数据变更日志之后,将其应用到本地来追赶主节点。之后就和正常情况一样持续接收来自主节点数据流的变化。
  • 主节点失效:节点切换。选择某个从节点将其提升为主节点;客户端也需要更新,这样之后的写请求会发送给新的主节点,然后其他从节点要接受来自新的主节点上的变更数据,这一过程称之为切换。

复制滞后问题(主从延迟)。

从一个从节点读取数据,而该副本落后于主节点,则应用可能会读到过期的信息。这会导致数据库中出现明显的不一致。
经过一段时间后,从节点最终会赶上并与主节点保持一致。也称为最终一致性。
解决复制滞后问题,可以通过写后读一致性、单调读、前缀一致读。

写后读一致性。

用户在写入不久即查看数据,则新数据可能尚未到达从节点。对用户来讲,看起来似乎是刚刚提交的数据丢失了,显然用户不会高兴。需要保证"写后读一致性"。
基于主从复制的系统,该如何实现写后读一致性呢?

  • 如果用户访问可能会被修改的内容,从主节点读取;否则,在从节点读取。一个简单的规则:总是从主节点读取用户自己的配置文件,而在从节点读取其他用户的配置文件。
  • 跟踪最近更新的时间,如果更新后一分钟之内,则总是在主节点读取;并监控从节点的复制滞后程度,避免从那些滞后时间超过一分钟的从节点读取。
  • 客户端还可以记住最近更新时的时间戳,并附带在读请求中,据此信息,系统可以确保对该用户提供读服务时都应该至少包含了该时间戳的更新。如果不够新,要么交由另一个副本来处理,要么等待直到副本接收到了最近的更新。
  • 如果副本分布在多数据中心(例如考虑与用户的地理接近,以及高可用性),情况会更复杂些。必须先把请求路由到主节点所在的数据中心(该数据中心可能离用户很远)。

单调读

  • 用户在某个时间点读到数据后,保证此后不会出现比该时间点更早的数据。

前缀一致读

  • 保证数据之间的因果关系,例如,总是以正确的顺序先读取问题,然后看到回答。

多主节点复制

  • 主从复制存在一个明显的缺点:系统只有一个主节点,而所有写入都必须经由主节点。如果由于某种原因,例如与主节点之间的网络中断而导致主节点无法连接,主从复制方案就会影响所有的写入操作。
    多主节点复制,系统存在多个主节点,每个都可以接收写请求,客户端将写请求发送到其中的一个主节点上,由该主节点负责将数据更改事件同步到其他主节点和自己的从节点。

无主节点复制

  • 无主节点复制:客户端将写请求发送到多个节点上,读取时从多个节点上并行读取,以此检测和纠正某些过期数据。

第六章 数据分区

  • 分区通常是这样定义的,即每一条数据(或者每条记录,每行或每个文档)只属于某个特定分区。

采用数据分区的主要目的是提高可扩展性。

分区的主要目标是将数据和查询负载均匀分布在所有节点上。

数据分区与数据复制。

分区通常与复制结合使用。即每个分区在多个节点都存有副本。
一个节点,可能是某些分区的主副本,同时也是其他分区的从副本。

分区的方式有以下几种:

  • 键值数据的分区。
    如果分区不均匀,则会出现某些分区节点比其他分区承担更多的数据量或查询负载,称之为倾斜。倾斜会导致分区效率严重下降。

  • 基于关键字区间的分区。
    一种分区方式是为每个分区分配一段连续的关键字或者关键字区间范围(以最小值和最大值来指示)。
    基于关键字的区间分区的缺点是某些访问模式会导致热点。

  • 基于关键字哈希值分区。
    对于上述数据倾斜与热点问题,许多分布式系统采用了基于关键字哈希函数的方式来分区。
    一个好的哈希函数可以处理数据倾斜并使其均匀分布。
    一旦找到合适的关键字哈希函数,就可以为每个分区分配一个哈希范围(而不是直接作用于关键字范围),关键字根据其哈希值的范围划分到不同的分区中。

负载倾斜与热点。

基于哈希的分区方法可以减轻热点,但无法做到完全避免。一个极端情况是,所有的读/写操作都是针对同一个关键字,则最终所有请求都将被路由到同一个分区。
例如,社交媒体网站上,一些名人用户有数百万的粉丝,当其发布一些热点事件时可能会引发一场访问风暴,出现大量的对相同关键字的写操作(其中关键字可能是名人的用户ID,或者人们正在评论的事件)。
此时,哈希起不到任何帮助作用,因为两个相同的哈希值仍然相同。
大多数的系统今天仍然无法自动消除这种高度倾斜的负载,而只能通过应用层来减轻倾斜程度。
例如,如果某个关键字被确认为热点,一个简单的技术就是在关键字的开头或结尾处添加一个随机数。只需一个两位数的十进制随机数就可以将关键字的写操作分布到100个不同的关键字上,从而分配到不同的分区上。

分区再平衡

所有这些变化都要求数据和请求可以从一个节点转移到另一个节点。这样一个迁移负载的过程称为再平衡(或者动态平衡)。
无论对于哪种分区方案,分区再平衡通常至少要满足:
(1)平衡之后,负载、数据存储、读写请求等应该在集群范围更均匀地分布。
(2)再平衡执行过程中,数据库应该可以继续正常提供读写服务。避免不必要的负载迁移,以加快动态再平衡,并尽量减少网络和磁盘I/O影响。

  • 为什么不用取模?
    对节点数取模方法的问题是,如果节点数N发生了变化,会导致很多关键字需要从现有的节点迁移到另一个节点。例如,假设hash(ky)=123456,假定最初是10个节点,那么这个关键字应该放在节点6(123456m0d10=6);当节点数增加到11时,它需要移动到节点3(123456mod11=3);当继续增长到12个节点时,又需要移动到节点0(123456m0d12=0)。这种频繁的迁移操作大大增加了再平衡的成本。

  • 固定数量的分区。
    首先,创建远超实际节点数的分区数,然后为每个节点分配多个分区。例如,对于一个10节点的集群,数据库可以从一开始就逻辑划分为1000个分区,这样大约每个节点承担100个分区。
    接下来,如果集群中添加了一个新节点,该新节点可以从每个现有的节点上匀走几个分区,直到分区再次达到全局平衡。

  • 动态创建分区。
    当分区的数据增长超过一个可配的参数阈值,它就拆分为两个分区,每个承担一半的数据量。
    相反,如果大量数据被删除,并且分区缩小到某个阈值以下,则将其与相邻分区进行合并。该过程类似于B树的分裂操作。
    每个分区总是分配给一个节点,而每个节点可以承载多个分区,这点与固定数量的分区一样。当一个大的分区发生分裂之后,可以将其中的一半转移到其他某节点以平衡负载。
    动态分区的一个优点是分区数量可以自动适配数据总量。如果只有少量的数据,少量的分区就足够了,这样系统开销很小;如果有大量的数据,每个分区的大小则被限制在一个可配的最大值。

  • 按节点比例分区。
    分区数与集群节点数成正比关系。换句话说,每个节点具有固定数量的分区。
    此时,当节点数不变时,每个分区的大小与数据集大小保持正比的增长关系;当节点数增加时,分区则会调整变得更小。较大的数据量通常需要大量的节点来存储,因此这种方法也使每个分区大小保持稳定。
    当一个新节点加入集群时,它随机选择固定数量的现有分区进行分裂,然后拿走这些分区的一半数据量,将另一半数据留在原节点。随机选择可能会带来不太公平的分区分裂,但是当平均分区数量较大时,新节点最终会从现有节点中拿走相当数量的负载。

请求路由

将数据集分布到多个节点上,当客户端需要发送请求时,如何知道应该连接哪个节点?如果发生了分区再平衡,分区与节点的对应关系随之还会变化。
几种不同的处理策略:
1.允许客户端链接任意的节点。如果某节点恰好拥有所请求的分区,则直接处理该请求;否则,将请求转发到下一个合适的节点,接收答复,并将答复返回给客户端。
2.将所有客户端的请求都发送到一个路由层,由后者负责将请求转发到对应的分区节点上。路由层本身不处理任何请求,它仅充一个分区感知的负载均衡器。
3.客户端感知分区和节点分配关系。此时,客户端可以直接连接到目标节点,而不需要任何中介。

许多分布式数据系统依靠独立的协调服务(如ZooKeeper)跟踪集群范围内的元数据。每个节点都向ZooKeeper中注册自己,ZooKeeper维护了分区到节点的最终映射关系。其他参与者(如路由层或分区感知的客户端)可以向ZooKeeper订阅此信息。一旦分区发生了改变,或者添加、删除节点,ZooKeeper就会主动通知路由层,这样使路由信息保持最新状态。
Cassandra和Riak则采用了不同的方法,它们在节点之间使用gossip协议来同步群集状态的变化。请求可以发送到任何节点,由该节点负责将其转发到目标分区节点。这种方式增加了数据库节点的复杂性,但是避免了对ZooKeeper.之类的外部协调服务的依赖。

七、事务

  • 事务将应用程序的多个读、写操作捆绑在一起成为一个逻辑操作单元。
    即事务中的所有读写是一个执行的整体,整个事务要么成功(提交)、要么失败(中止或回滚)。如果失败,应用程序可以安全地重试。
  • ACID:事务所提供的ACID,分别代表原子性(Atomicity),一致性(Consistency),隔离性(Isolation)与持久性(Durability),取这四个特性的首字母。
  • 原子性:描述了客户端发起一个包含多个写操作的请求时可能发生的情况,例如在完成了一部分写入之后,系统发生了故障,包括进程崩溃,网络中断,磁盘变满或者违反了某种完整性约束等;把多个写操作纳入到一个原子事务,万一出现了上述故障而导致没法完成最终提交时,则事务会中止,并且数据库须丢弃或撤销那些局部完成的更改。
    假如没有原子性保证,当多个更新操作中间发生了错误,就需要知道哪些更改已经生效,哪些没有生效,这个寻找过程会非常麻烦。或许应用程序可以重试,但情况类似,并且可能导致重复更新或者不正确的结果。原子性则大大简化了这个问题:如果事务已经中止,应用程序可以确定没有实质发生任何更改,所以可以安全地重试。
  • 一致性:主要是指对数据有特定的预期状态,任何数据更改必须满足这些状态约束(或者恒等条件)。例如,对于一个账单系统,账户的贷款余额应和借款余额保持平衡。如果某事务从一个有效的状态开始,并且事务中任何更新操作都没有违背约束,那么最后的结果依然符合有效状态。
  • 隔离性:意味着并发执行的多个事务相互隔离,它们不能互相交叉。虽然实际上它们可能同时运行,但数据库系统要确保当事务提交时,其结果与串行执行(一个接一个执行)完全相同。
  • 持久性:数据库系统本质上是提供一个安全可靠的地方来存储数据而不用担心数据丢失等。持久性保证一旦事务提交成功,即使存在硬件故障或数据库崩溃,事务所写入的任何数据也不会消失。

弱隔离级别

读-提交。

读提交是最基本的事务隔离级别,它只提供以下两个保证:
1.读数据库时,只能看到已成功提交的数据(防止“脏读”)。
2.写数据库时,只会覆盖已成功提交的数据(防止“脏写”)。

  • 防止脏读。
    假定某个事务已经完成部分数据写入,但事务尚未提交(或中止),此时另一个事务是否可以看到尚未提交的数据呢?如果是的话,那就是脏读。
    读提交级别的事务隔离必须做到防止发生脏读。这意味着事务的任何写入只有在成功提交之后,才能被其他人观察到(并且所有的写全部可见)。

  • 防止脏写。
    如果两个事务同时尝试更新相同的对象,后写的操作会覆盖较早的写入。
    但是,如果先前的写入是尚未提交事务的一部分,是否还是被覆盖?如果是,那就是脏写。
    读提交隔离级别下所提交的事务可以防止脏写,通常的方式是推迟第二个写请求,直到前面的事务完成提交(或者中止)。

  • 实现读-提交。
    数据库通常采用行级锁来防止脏写:当事务想修改某个对象(例如行或文档)时,它必须首先获得该对象的锁;然后一直持有锁直到事务提交(或中止)。给定时刻,只有一个事务可以拿到特定对象的锁,如果有另一个事务尝试更新同一个对象,则必须等待,直到前面的事务完成了提交(或中止)后,才能获得锁并继续。这种锁定是由处于读提交模式(或更强的隔离级别)数据库自动完成的。

因此,大多数数据库采用了这种方法来防止脏读:对于每个待更新的对象,数据库都会维护其旧值和当前持锁事务将要设置的新值两个版本。在事务提交之前,所有其他读操作都读取旧值;仅当写事务提交之后,才会切换到读取新值。

快照级别隔离与可重复读

快照级别隔离。其总体想法是,每个事务都从数据库的一致性快照中读取,事务一开始所看到是最近提交的数据,即使数据随后可能被另一个事务更改,但保证每个事务都只看到该特定时间点的旧数据。

快照级别隔离对于长时间运行的只读查询(如备份和分析)非常有用。如果数据在执行查询的同时还在发生变化,那么查询结果对应的物理含义就难以理清。而如果查询的是数据库在某时刻点所冻结的一致性快照,则查询结果的含义非常明确。

考虑到多个正在进行的事务可能会在不同的时间点查看数据库状态,所以数据库保留了对象多个不同的提交版本,这种技术因此也被称为多版本并发控制(Multi- Version Concurrency Control,MVCC)
支持快照级别隔离的存储引擎往往直接采用MVCC来实现读-提交隔离。典型的做法是,在读-提交级别下,对每一个不同的查询单独创建一个快照;而快照级别隔离则是使用一个快照来运行整个事务。

防止更新丢失

  • 更新丢失可能发生在这样一个操作场景中:应用程序从数据库读取某些值,根据应用逻辑做出修改,然后写回新值(read-modify-writ过程)。当有两个事务在同样的数据对象上执行类似操作时,由于隔离性,第二个写操作并不包括第一个事务修改后的值,最终会导致第一个事务的修改值可能会丢失。

  • 原子写操作
    许多数据库提供了原子更新操作,以避免在应用层代码完成“读-修改-写回”操作,如果支持的话,通常这就是最好的解决方案。
    例如,以下指令在多数关系数据库中都是并发安全的:

UPDATE counters SET value=value +1 WHERE key='foo';
  • 显式加锁
    如果数据库不支持内置原子操作,另一种防止更新丢失的方法是由应用程序显式锁定待更新的对象。然后,应用程序可以执行“读-修改-写回”这样的操作序列;此时如果有其他事务尝试同时读取对象,则必须等待当前正在执行的序列全部完成。
SELECT FROM figures WHERE name ='robot'AND game_id =222 FOR UPDATE;

FOR UPDATE指令指示数据库对返回的所有结果行要加锁。

  • 自动检测更新丢失:
    MySQL/ InnoDB的可重复读却并不支持检测更新丢失。

  • 原子比较和设置
    原子比较和设置在不提供事务支持的数据库中,有时你会发现它们支持原子“比较和设置”操作。使用该操作可以避免更新丢失,即只有在上次读取的数据没有发生变化时才允许更新;如果已经发生了变化,则回退到“读-修改-写回”方式。

 UPDATE wiki_pages SET content ='new content' WHERE id=1234 AND content ='old content';

写倾斜与幻读

可以将写倾斜视为一种更广义的更新丢失问题。即如果两个事务读取相同的一组对象,然后更新其中一部分:不同的事务可能更新不同的对象,则可能发生写倾斜;而不同的事务如果更新的是同一个对象,则可能发生脏写或更新丢失。

在一个事务中的写入改变了另一个事务查询结果的现象,称为幻读。

串行化

解决并发问题最直接的方法是避免并发:即在一个线程上按顺序方式每次只执行一个事务。这样我们完全回避了诸如检测、防止事务冲突等问题,其对应的隔离级别一定是严格串行化的。

第八章 分布式系统的挑战

故障与部分失效

  • 在分布式系统中,可能会出现系统的一部分工作正常,但其他某些部分出现难以预测的故障,我们称之为“部分失效”。问题的难点就在于这种部分失效是不确定的:如果涉及多个节点和网络,几乎肯定会碰到有时网络正常,有时则莫名的失败。正如接下来马上要看到的,通过网络发送消息的延迟非常不确定,有时甚至根本不知道执行是否成功。

不能假定故障不可能发生而总是期待理想情况。最好仔细考虑各种可能的出错情况,包括那些小概率故障,然后尝试人为构造这种测试场景来充分检测系统行为。

不可靠的网络

互联网以及大多数数据中心的内部网络(通常是以太网)都是异步网络。在这种网络中,一个节点可以发送消息(数据包)到另一个节点,但是网络并不保证它什么时候到达,甚至它是否一定到达。发送之后等待响应过程中,有很多事情可能会出错:
1.请求可能已经丢失(比如有人拔掉了网线)
2.请求可能正在某个队列中等待,无法马上发送(也许网络或接收方已经超负荷)。
3.远程接收节点可能已经失效(例如崩溃或关机)。
4.远程接收节点可能暂时无法响应(例如正在运行长时间的垃圾回收,请参阅本章后面的“进程暂停”)。
5.远程接收节点已经完成了请求处理,但回复却在网络中丢失(例如网络交换机配置错误)。
6.远程接收节点已经完成了请求处理,但回复却被延迟处理(例如网络或者发送者的机器超出负荷)。

发送者甚至不清楚数据包是否完成了发送,只能选择让接收者来回复响应消息,但回复也有可能丢失或延迟。这些问题在一个异步网络中无法明确区分,发送者拥有的唯一信息是,尚未收到响应,但却无法判定具体原因。处理这个问题通常采用超时机制。
超时机制:在等待一段时间之后,如果仍然没有收到回复则选择放弃,并且认为响应不会到达。
但是,即使判定超时,仍然并不清楚远程节点是否收到了请求(一种情况,请求仍然在某个地方排队,即使发送者放弃了,但最终请求会发送到接收者)。

第九章 一致性与共识

  • 可线性化(也称为原子一致性,强一致性等)的思想。其基本的想法是让一个系统看起来好像只有一个数据副本,且所有的操作都是原子的。有了这个保证,应用程序就不需要关心系统内部的多个副本。
  • 如何达到线性化?可线性化背后的基本思想很简单:使系统看起来好像只有一个数据副本。
  • 什么时候应该使用线性化?
    (1)加锁与主节点选举。
    主从复制的系统需要确保有且只有一个主节点,否则会产生脑裂。选举新的主节点常见的方法是使用锁:即每个启动的节点都试图获得锁,其中只有一个可以成功即成为主节点。不管锁具体如何实现,它必须满足可线性化:所有节点都必须同意哪个节点持有锁,否则就会出现问题。
    (2)约束与唯一性保证。唯一性约束在数据库中很常见。
  • 线性化的缺点。线性化会显著降低性能和可用性,尤其是在严重网络延迟的情况下(例如多数据中心)。正因如此,一些分布式数据系统已经放弃了线性化,以换来更好的性能,但也存在可能无法正确工作的风险。
  • 顺序保证。顺序与因果关系。因果关系对所发生的事件施加了某种排序:发送消息先于收到消息;问题出现在答案之前等,或者就像在现实生活中一样,一件事情会导致另一件事情,诸如此类。
    因果关系的依赖链条定义了系统中的因果顺序,即某件事应该发生另一件事情之前。
  • 序列号排序。可以使用序列号或时间戳来排序事件。也就是说,每一个操作都有唯一的顺序号,并且总是可以通过比较来确定哪个更大(即操作发生在后)。
    特别是,我们可以按照与因果关系一致的顺序来创建序列号:保证如果操作A发生在B之前,那么A一定在全序中出现在B之前(即A的序列号更小)。并行操作的序列可能是任意的。这样的全局排序可以捕获所有的因果信息,但也强加了比因果关系更为严格的顺序性。

分布式事务与共识

两阶段提交。

  • 两阶段提交(two-phase commit,.2PC)是一种在多节点之间实现事务原子提交的算法,用来确保所有节点要么全部提交,要么全部中止。它是分布式数据库中的经典算法之一.2PC在某些数据库内部使用,或者以XA事务形式或SOAP Webj服务WS-AtomicTransaction的形式提供给应用程序。
    2PC的基本流程如图9-9所示。不同于单节点上的请求提交,2PC中的提交/中止过程分为两个阶段(因此得名2PC)。

2PC引入了单节点事务所没有的一个新组件:协调者(也称为事务管理器)。
协调者通常实现为共享库,运行在请求事务相同进程中,但也可以是单独的进程或服务。
通常,2PC事务从应用程序在多个数据库节点上执行数据读/写开始。
我们将这些数据库节点称为事务中的参与者。
当应用程序准备提交事务时,协调者开始阶段1:发送一个准备请求到所有节点,询问他们是否可以提交。
协调者然后跟踪参与者的回应:
如果所有参与者回答“是”,表示他们已准备好提交,那么协调者接下来在阶段2会发出提交请求,提交开始实际执行。
如果有任何参与者回复“否”,则协调者在阶段2中向所有节点发送放弃请求。

  • 协调者发生故障
    如果参与者或者网络在2PC期间发生失败,例如在第一阶段,任何一个准备请求发生了失败或者超时,那么协调者就会决定中止交易;或者在第二阶段发生提交(或中止)请求失败,则协调者将无限期重试。但是,如果协调者本身发生了故障,接下来会发生什么现在还不太清楚。如果协调者在发送准备请求之前就已失败,则参与者可以安全地中止交易。但是,一旦参与者收到了准备请求并做了投票“是”,则参与者不能单方面放弃,它必须等待协调者的决定。如果在决定到达之前,出现协调者崩溃或网络故障,则参与者只能无奈等待。此时参与者处在一种不确定的状态。

三阶段提交。

两阶段提交也被称为阻塞式原子提交协议,因为2PC可能在等待协调者恢复时卡住。理论上,可以使其改进为非阻塞式从而避免这种情况。但是,实践中要想做到这一点并不容易。作为2PC的替代方案,目前也有三阶段提交算法。然而,3PC假定一个有界的网络延迟和节点在规定时间内响应。考虑到目前大多数具有无限网络延迟和进程暂停的实际情况(见第8章),它无法保证原子性。

待补充。