Skip to the content.

No compromises: distributed transactions with consistency, availability, and performance

abstract

强一致性和高可用性的事务对于简化分布式系统的构建和推理至关重要。然而,以前的实现性能表现得太差了,真是让人摸不着头脑。这让系统设计师不得不做出妥协,要么完全避免使用事务,要么放松一致性保证,或者只能提供需要程序员进行数据分区的单机事务。这简直是一团乱麻!但是别担心,本文要向大家介绍一款分布式计算平台的超级英雄:FaRM,他是一个内存型分布式计算平台!这家伙能够提供严格可串行化的分布式事务,性能超群,耐久性和高可用性样样俱全。在一个拥有4.9 TB数据库的90台机器上,FaRM的峰值吞吐量可达每秒1.4亿个TATP(事务处理性能委员会)事务!此外,FaRM还能以不到50毫秒的时间从故障中恢复。这种弹性简直让人瞠目结舌!这一切的秘诀在于全新设计的事务、复制和恢复协议,充分利用了那些高级玩意儿RDMA(远程直接内存访问)的通用网络,以及提供非易失性DRAM存储的崭新、低成本方法。

DRAM一般用作内存,SRAM一般用作二级CACHE,本文介绍的FaRM是一个内存型、分布式数据库系统。

1. Introduction

高可用性和严格可串行化的事务[35]能够通过提供一个简单却强大的抽象,让我们在分布式系统中的编程和推理过程变得轻而易举!就像有一个从不出问题、按照实时顺序一次执行一个事务的超级单机系统。不过,以前在分布式系统中尝试实现这种抽象的结果可真是糟糕透了,性能简直让人崩溃。所以,有些系统,比如Dynamo [13]或Memcached [1]为了提高性能,要么不支持事务,要么实现了弱一致性保证。而其他一些系统(比如[3–6, 9, 28]),只有当所有数据都在一个机器上时才能提供事务支持,这迫使程序员对数据进行分区,搞得正确性推理复杂化了不少。

哇哦,这篇论文实际上是在给我们上一堂硬核课程,讲述了FaRM [16]这个内存式分布式计算平台中的事务、复制和恢复协议!它能够提供分布式ACID事务,严格可串行化,高可用性,高吞吐量,还低延迟哦!这些协议是从头开始设计的,充分利用了现代数据中心中的两个硬件趋势:带有RDMA的快速通用网络,还有提供非易失性DRAM的低成本方法。为了实现非易失性,它们居然在电源装置上连接了电池,断电时将DRAM的内容写入SSD。这些趋势不仅消除了存储和网络的瓶颈,还暴露了限制性能的CPU瓶颈。但是,FaRM的协议采取了三个原则来应对这些CPU瓶颈:减少消息数量,使用单向RDMA读写代替消息,还有高效地利用并行性。

注意DRAM本身是易失性存储,但是FaRM中通过用电池给DRAM的存储单元持续性供电,让其具有了一定的非易失性。

FaRM 是一个分布式系统,旨在通过将对象分布在多台机器上,同时支持涉及任意数量这些机器的事务,以提高数据中心的性能和可靠性。系统采用垂直 Paxos(与传统 Paxos 复制相比)这种主备份复制方法,使得消息数量减少,同时未复制的协调器可以直接与主服务器和备份服务器进行通信。在 FaRM 中,通过四阶段提交协议(锁定、验证、备份提交和主提交)来使用乐观并发控制。值得注意的是,FaRM 通过在锁定阶段消除对备份的消息需求来改进原始协议。这种优化使得在管理分布式事务方面更加高效和简化。

FaRM通过使用单向RDMA操作进一步降低了CPU开销!**单向RDMA不需要使用远程CPU,并且避免了大部分本地CPU开销。**在FaRM的事务执行和验证过程中,它们使用单向RDMA读取,因此在只读参与者的远程机器上不需要使用CPU。顺便一提,协调者在将事务中修改的对象的副本记录到非易失性log时,也使用单向RDMA。比如说,协调者只需要一个单向RDMA就能把提交记录写到远程备份上。这意味着,在备份中是不需要使用前台CPU的。当然,稍后会在后台使用CPU来懒洋洋地截断日志,以原地更新对象。

使用单向RDMA的时候,我们需要新的故障恢复协议。因为FaRM不能依赖服务器在租约[18]到期时拒绝传入的请求,毕竟请求是由不支持租约的网卡提供服务的嘛。那么我们是怎么解决这个问题的呢?我们使用精确成员资格[10](`precise membership`),确保机器在当前配置成员资格上达成一致,并且只向成员机器发送单向操作。此外,FaRM不能像传统方法那样确保参与者在准备阶段拥有提交事务所需的资源,因为事务记录是在不涉及远程CPU的情况下写入参与者日志的。作为解决方案,FaRM使用预留机制,在开始提交前,确保日志具有充足的空间容纳所有提交和截断事务所需的记录。

FaRM的故障恢复协议速度之所以快,归功于其有效利用并行机制。它将状态恢复工作平均分布在集群的各个节点上,并在每个机器的内核中进行恢复操作的并行处理。另外,它采用两种优化方法,实现事务处理和恢复并行进行。首先,在短短数十毫秒的锁恢复阶段后,事务便开始访问受故障影响的数据,而无需等候数秒钟的其他恢复过程。其次,那些未受故障影响的事务可在无阻塞的情况下继续执行。FaRM还利用了快速网络交换心跳信号,实现对故障的快速检测,同时通过优先级和预分配策略来避免误报。

实验结果表明,您可以在一致性、高可用性和性能之间获得完美平衡(你可以获得一切)。FaRM可以在不到50毫秒的时间里从单台机器故障恢复,并在仅使用几台机器的情况下,实现比最先进的单机内存事务系统更好的性能。例如,FaRM在仅运行在三台机器上时,其吞吐量就超过了Hekaton [14, 26],并且在吞吐量和延迟方面都优于Silo [39, 40]。

2. Hardware trends

FaRM的设计初衷是充分利用数据中心机器中丰富、低成本的DRAM。典型的数据中心配置中,每台双插槽机器的DRAM容量为128GB至512GB [29],且DRAM的成本低至12美元/GB。这意味着只需要2000台机器就可以实现1PB(1兆兆字节)的DRAM,足以容纳许多有趣应用的数据集。另外,FaRM充分利用了两大硬件趋势,以消除存储和网络瓶颈:非易失性DRAM和支持RDMA的高速普通网络。

2.1 Non-volatile DRAM

分布式不间断电源(UPS)利用锂离子电池的广泛可用性,降低了数据中心UPS的成本,相对于传统的,采用铅酸电池的集中式方法。例如,微软的Open CloudServer(OCS)规范包括本地能源存储(LES)[30, 36],它将锂离子电池与机架内每个24台机械设备的电源单元集成在一起。估计LES UPS的成本不到每焦耳0.005美元。² 这种方法比传统的UPS更可靠:锂离子电池进行了超额备用并具有多个独立电池单元,电池发生故障时仅会影响机架的一部分。

分布式不间断电源(UPS)有效地使 DRAM 具有持久性。当发生电源故障时,分布式 UPS 使用来自电池的能量将内存的内容保存到普通 SSD 中。这不仅通过避免对 SSD 进行同步写入来提高常规性能,而且还通过仅在发生故障时对其进行写入来保持 SSD 的使用寿命。另一种方法是使用非易失性 DIMM(NVDIMM),它们包含自己的私有闪存、控制器和超级电容器(例如 [2])。不幸的是,这些设备专用、昂贵且笨重。相比之下,分布式 UPS 使用商品 DIMM 并利用商品 SSD。唯一的额外成本是 SSD 上的保留容量和 UPS 电池本身。

电池供应成本取决于将内存保存到 SSD 所需的能量。我们在一台标准的 2 插槽机器上测量了一个未优化的原型。在故障发生时,它会关闭硬盘驱动器和网络接口卡,并将内存数据保存到单个 M.2(PCIe)SSD,每保存 1GB 数据消耗约 110 焦耳的能量。在保存过程中,大约 90 焦耳用于给机器上的两个 CPU 插槽供电。额外的 SSD 可以减少保存数据所需的时间,从而降低能量消耗(图 1)。通过优化,例如将 CPU 设置成低功耗状态,将进一步降低能耗。

image-20230605101822693

在最坏的配置(单个SSD,无优化)下,以每焦耳0.005美元计算,非易失性能源成本为每GB0.55美元,保留SSD容量的存储成本为每GB0.90美元。这两者的总额外成本低于基本DRAM成本的15%,这比NVDIMM(其成本为DRAM的3-5倍)有显著改进。因此,将所有机器内存视为非易失性RAM(NVRAM)是可行且经济高效的。FaRM将所有数据存储在内存中,并在多个副本的NVRAM上写入后将其视为是被持久化的。

2.2 RDMA networking

FaRM 尽可能使用单向 RDMA 操作,因为它们不使用远程 CPU(根本上在于RDMA的操作不用通过操作系统)。我们根据先前的工作和其他测量结果做出了这一决定。在 [16]中,我们表明在一个 20 台机器的 RoCE [22] 集群中,与所有机器从集群中其他机器随机选择的小对象上的可靠 RPC over RDMA 相比,RDMA 读取性能提高了2倍。瓶颈是 NIC 消息速率,我们的 RPC 实现需要的消息数是单向读取的两倍。我们在一个90台机器的集群上重复了这个实验,每台机器有两个 Infiniband FDR(56 Gbps)的 NIC。与 [16] 相比,这使得每台机器的消息速率增加了一倍多,消除了 NIC 消息速率的瓶颈。现在 RDMA 和 RPC 都受到 CPU 的限制,性能差距增加到4倍,如图 2 所示。这说明了降低 CPU 开销对于实现新硬件潜力的重要性。

image-20230605102641824

3.programming model and architecture

FaRM 为应用程序提供了一个跨集群机器的全局地址空间抽象。每台机器运行应用程序线程并在地址空间中存储对象。FaRM API [16] 在事务中为本地和远程对象提供透明访问。应用程序线程可以随时启动事务,并成为事务的协调者。在事务执行期间,线程可以执行任意逻辑以及读取、写入、分配和释放对象。执行结束时,线程调用 FaRM 提交事务。

FaRM事务使用乐观并发控制。执行过程中的更新会在本地缓冲,并且只有在成功提交后才对其他事务可见。提交可能因为与并发事务的冲突或失败而失败。FaRM保证了所有成功提交的事务的严格可串行化[35]。在事务执行过程中,FaRM保证单个对象的读取是原子性的,它们只读取已提交的数据,对同一对象的连续读取返回相同的数据,对事务写入的对象的读取返回最新写入的值。**它不保证不同对象之间的读取是原子性的**,但在这种情况下,它保证事务不会提交,确保已提交事务是严格可串行化的。这使我们可以将一致性检查推迟到提交时间,而不是在每次对象读取时重新检查一致性。然而,这增加了一些编程复杂性:FaRM应用程序必须在执行过程中处理这些临时不一致性[20]。可以自动处理这些不一致性[12]。

在FaRM中,它不能保证不同对象之间的读取是原子性的,主要是因为FaRM使用乐观并发控制策略。这种策略允许事务并行执行,从而提高系统的吞吐量。然而,这也意味着不同事务可能同时操作相同的数据,因此可能导致短暂的不一致。遥控相同对象更新可能不能保证事务的原子性。这种策略需要在事务提交时检查并解决这些短暂的不一致。

FaRM API 也提供无锁读取,这是一种针对单个对象的只读事务的优化,以及本地性提示的优化,使程序员可以在同一组机器上共同定位相关对象。应用程序可以利用这些特性来提高性能,如在论文 [16] 所描述的那样。

image-20230612102139633

图 3 展示了一个包括四台机器的 FaRM 实例。同时,该图还展示了机器 A 的内部组成部分。每台机器在一个用户进程中运行 FaRM,并将内核线程固定到每个硬件线程上。每个内核线程都运行一个事件循环,用于执行应用程序代码并轮询 RDMA 完成队列。

随着时间的推移,FaRM 实例随着机器故障或新机器的添加而在一系列配置中移动。配置是一个元组〈i, S, F, CM〉,其中 i 是唯一的、单调递增的 64 位配置标识符;S 是配置中的机器集合;F 是从机器映射到预期独立失败的故障域的映射(例如,不同的机架);而 CM ∈ S 是配置管理器。FaRM 使用 Zookeeper [21] 协调服务,来确保机器就当前配置达成一致并将其存储,如同在垂直 Paxos [25] 中所述。但它不像通常那样依赖Zookeeper来管理租约、检测故障或协调恢复。CM 利用基于 RDMA 的高效实现来快速恢复。每次配置更改,CM都会调用Zookeeper一次,以更新配置。

在计算机系统中,故障域(failure domain)是指那些在某种故障条件下可能同时出现故障的组件集合。故障域有助于更好地理解和评估系统中的故障风险。一般来说,故障域可以是物理设备、网络连接、电源或任何可以影响系统正常运行的设施。在系统部署和设计过程中,通过将机器映射到不同的故障域,可以增加冗余度,提高系统容错能力。

在 FaRM 中,全局地址空间包括很多个 2GB 大小的区域,每个区域在一个主副本和 f 个备份副本上复制,其中 f 是期望的容错容量。每台机器将多个区域(regions)存储在可以通过 RDMA 供其他机器读取的非易失性 DRAM 中。对象始终从包含该区域的主副本中读取,如果区域位于本地机器上,则使用本地内存访问;如果区域位于远程,则使用单向RDMA 读取。

是的,这里的意思是在 FaRM 系统中,全局地址空间被分成多个大小为 2GB 的区域。对于每一个这样的区域,都有一个主副本和 f 个备份副本。这些副本分布在不同的机器上,以确保数据的可靠性和容错能力。

根据主要副本所在区域的位置(本地或远程),FaRM 系统会选择使用本地内存访问或单向 RDMA 读取方法来访问对象。这两种方法都旨在提供高性能的对象访问。

每个对象都有一个 64 位版本号,用于并发控制和复制。区域标识符到其主副本和备份副本的映射由 CM 维护并与区域一起复制。其他机器按需获取这些映射,并由线程缓存,同时也缓存了发往主副本的单向 RDMA 读取所需的 RDMA 引用。

在这里,提到的 “所需的 RDMA 引用” 指的是用于执行 RDMA 读取操作所需的某些关键信息。

远程直接内存访问(RDMA)是一种网络通信技术,它允许不同计算机之间在内存中直接读写对方的数据,**而无需通过操作系统**。要实现这一功能,需要在发起远程读操作的计算机上设置一些关键信息,例如:远程计算机的地址、需要访问的内存区域等。这些关键信息被称为 RDMA 引用。

机器在分配新区域时会联系CM(配置管理器)。CM会从单调递增的计数器中分配一个区域标识符,并为该区域选择副本。副本选择会在满足以下条件的情况下,在每台机器上平衡存储的区域数量:有足够的容量;每个副本位于不同的故障域;当应用程序指定局部性约束时,区域与目标区域共同定位。CM挑好副本后,给它们发带区域标识符的prepare消息。如果所有副本都报告成功分配了这个区域(即给这个区域预留好了空间),CM才会向它们发送提交消息。这种两阶段协议确保在开始使用映射之前,映射在所有区域副本中都是有效并被复制的。

这种集中式方法比我们以前基于一致性哈希的方法[16]更灵活地满足故障独立性和局部性约束。这也使得在各个机器之间平衡负载以及在接近容量限制下操作变得更简单。使用2 GB的区域(每个区域的大小都是固定2GB),我们预期在典型机器上最多可以容纳250个区域,因此单个CM可以处理数千台机器的区域分配。

集中式方法相较于基于一致性哈希的方法[16]为什么能在满足故障独立性和局部性约束方面提供更多的灵活性,具体原因主要有以下几点:

  1. 负载均衡:一致性哈希方法将资源分布在所有机器上,但在实际运行中,工作负载可能不均匀,导致某些节点过载。而集中式方法通过配置管理器(CM)利用全局视野动态地调整资源分配,从而实现更好的负载均衡。
  2. 容量利用:集中式方法可以更加有效地在接近容量限制下操作。它可以有效地平衡空闲容量和已用容量,确保资源在所有机器上得到合理分配。
  3. 局部性保证:相较于基于一致性哈希的方法,集中式方法在实现局部性保证方面更灵活。它可以根据目标资源与已有资源之间的距离和网络拓扑结构等信息来满足应用程序的局部性约束要求。

因此,集中式方法在满足故障独立性和局部性约束方面具有更高的灵活性,超越了基于一致性哈希的方法。

当我们讨论目标资源和已有资源时,我们通常是在讨论系统中需要分配和管理的资源。在分布式系统或数据中心中,这些资源可能包括内存、存储、计算能力等。

  1. 目标资源:目标资源通常是指应用程序或系统即将请求或需要使用的资源。在分配目标资源时,系统需要考虑资源的可用性、性能需求和其他约束条件(如故障域、局部性等)。
  2. 已有资源:已有资源是指系统中已被分配和使用的资源。这些资源可能已经在为其他应用程序或任务提供服务。在为新请求分配资源时,系统需要考虑已有资源的使用情况,以便有效地进行负载均衡和资源整合。

总之,在一个分布式系统或数据中心中进行资源分配时,目标资源和已有资源是两个关键因素,它们之间的关系会影响到系统的性能和稳定性。

4.Distributed txns and replication

FaRM(Fast Remote Memory)将事务和复制协议整合在一起,以提高性能。它比传统协议使用更少的消息,并利用单边RDMA读写来提高CPU效率和降低延迟。FaRM在非易失性DRAM中使用主备复制策略,既用于数据存储,也用于事务日志。同时,它使用未复制的事务协调器与主节点和备份节点直接通信。FaRM采用乐观并发控制和读验证,这与某些软件事务内存系统(例如TL2[15])相似。

image-20230619094950161

图4:在FaRM(Fast Remote Memory)的提交协议中,有一个协调器C,主节点位于P1、P2、P3,备份节点位于B1、B2、B3。节点P1和P2被读取和写入,节点P3仅被读取。我们用虚线表示RDMA读取,实线表示RDMA写入,点线表示硬件确认,矩形表示对象数据。

图4展示了FaRM(快速远程内存)事务的时间线,表1和表2列出了事务协议中所使用的所有日志记录和消息类型。在执行阶段,事务使用单向RDMA来读取对象,并在本地缓冲写入操作。同时,协调器还记录所有访问对象的地址和版本信息。当主节点和备份节点与协调器位于同一台机器上时,对象的读取和写入日志将使用本地内存访问而不是RDMA。在执行阶段结束时,FaRM尝试通过以下步骤提交事务:

  1. Lock:协调器会向每个负责写入对象的主节点的日志写入一个LOCK记录。该记录包含主节点上那些所有写入对象的版本和新值,以及所有写入对象所在区域的列表。主节点会尝试使用compare-and-swap锁定指定版本的对象,并处理这些记录。随后,它们会向协调器发送一条消息,报告所有锁定操作是否成功。如果事务读取对象后任何对象的版本发生了变化,或者当前该对象已被其他事务锁定,那么锁定操作可能会失败。在这种情况下,协调器会终止事务,并向所有主节点写入一条中止记录,然后将错误信息返回给应用程序。

  2. Validate协调器通过从主节点处读取事务读取但未修改的所有对象的版本来执行读验证。在这些被扫描的对象中,若任何对象发生了变化,验证就会失败,事务将被终止。验证默认情况下使用单向RDMA读取。对于持有超过tr个对象的主节点,验证是通过RPC完成的。阈值tr(目前为4)反映了执行RPC相对于执行RDMA读取的CPU成本。
  3. Commit backups:协调器向每个备份节点的非易失性日志(在Farm中指的是non-volatile Dram)中写入一个COMMIT-BACKUP记录,然后等待来自NIC硬件的确认,这个过程不会影响备份节点的CPU。`COMMIT-BACKUP`日志记录与LOCK记录具有相同的负载。

  4. Commit primaries:在收到所有COMMIT-BACKUP写入操作的确认之后,协调器会向每个主节点的日志中写入一个COMMIT-PRIMARY记录。当协调器收到至少一个此类记录(`COMMIT-PRIMARY`)的硬件确认(见上图中有这个流程)或在本地写入一条记录时,协调器会向应用程序报告完成情况。主节点在处理这些记录时,会更新对象的值,增加对象的版本,并解锁它们,从而暴露事务所提交的写入操作。

    在本地写入一条记录时:这里的“it”指代协调器,“one”指代本地写入的COMMIT-PRIMARY记录。这里的“行为”指的是协调器在本地写入一条COMMIT-PRIMARY记录。

  5. Truncate。备份和主服务器会在日志中保存记录,直到它们被截断。在从所有主服务器收到确认后,协调器会懒惰地在主服务器和备份服务器上截断日志。它通过将已经截断的事务的标识符嵌入其他日志记录来实现这一目标。在截断时,备份服务器将更新应用到它们的对象副本上。

下面附上表1与表2(使用的所有日志记录和消息类型):

表1-在事务协议中使用的日志记录类型。尚未截断的事务标识符的下界和用于截断的事务标识符都会附加在每个记录上

日志记录类型 内容
LOCK 事务ID、事务写入的所有对象所在区域的ID,以及目标主服务器负责的事务写入的所有对象的地址、版本和值。
COMMIT-BACKUP 内容与上面的LOCK record是相同的
COMMIT-PRIMARY 要提交的事务的ID
ABORT 要中止的事务的ID
TRUNCATE 尚未截断事务的最低事务ID界限,以及将要被截断的事务的ID。(low bound transaction ID for non-truncated transactions and transaction IDs to truncate)

表2-在事务协议中使用的消息类型。除了前两个之外,其他的仅在恢复过程中使用。

Message Type Contents
LOCK-REPLY 事务ID,结果会指示获取锁的操作是否成功
VALIDATE 从目标服务器读取的对象的地址和版本(当通过RDMA读取完成验证时,不发送这些信息)
NEED-RECOVERY 配置ID、区域ID和需要恢复的事务ID(由备份发送至主服务器)
FETCH-TX-STATE 配置ID、区域ID和请求状态的事务ID(由主服务器发送至备份)
SEND-TX-STATE 配置ID、区域ID、事务ID,还有FETCH要求的事务锁记录内容(可以理解为对FETCH-TX-STATE命令的回复)
REPLICATE-TX-STATE 配置ID、区域ID、事务ID和锁记录内容(由主服务器发送至备份)
RECOVERY-VOTE 配置ID、区域ID、事务ID,被事务修改的区域的区域ID以及投票
REQUEST-VOTE 配置ID、事务ID、区域ID
COMMIT-RECOVERY 配置ID、事务ID
ABORT-RECOVERY 配置ID、事务ID
TRUNCATE-RECOVERY 配置ID、事务ID

4.1 Correctness

提交的读写事务在获取所有写锁的地方可串行化,提交的只读事务在进行最后一次读取的地方可串行化。这是因为在串行化点,所有读取和写入对象的版本都与执行过程中看到的版本相同。锁确保了已写入对象的这一点,验证确保了仅读取对象(即在事务执行中只读取但不会被修改的对象)的这一点。在没有故障的情况下,这相当于在串行化点执行并原子性地提交整个事务。FaRM中的串行性也是严格的:串行化点始终位于执行开始和完成并向应用程序报告之间。

为了确保在故障情况下的串行性,需要在写入COMMIT-PRIMARY之前等待所有备份的硬件确认。假设协调器没有收到某个备份b对区域r的确认。那么主服务器可能会暴露事务修改,后来与协调器和r的其他副本一起发生故障,而b从未接收到COMMIT-BACKUP记录。这将导致r的更新丢失。

由于读集仅存储在协调器中,如果协调器发生故障且无提交记录存活以证明验证成功,事务将被中止。因此,协调器需要在某个主服务器成功提交前等待,并向应用程序报告成功提交。这确保了对于向应用程序报告的提交事务,至少有一个提交记录在发生任何f故障后存活下来。要不然,如果协调器和所有备份在任何COMMIT-PRIMARY记录被写入前都挂了,那这种事务还是会被中止,因为只有LOCK记录留存,而且也没有证明验证成功过的记录。

在传统的两阶段提交协议中,参与者在处理prepare消息时可以预留资源以提交事务,或者在资源不足时拒绝准备事务(拒绝协调器的prepare消息)。然而,因为我们的协议避免在commit过程中涉及备份服务器的CPU,协调器必须在所有参与者处预留日志空间以保证进展。协调器在开始`commit`协议前预留所有提交协议记录的空间,同时也为主服务器和备份日志中的截断记录预留了空间。

日志预留是协调器的本地操作,因为协调器在各参与者分别拥有的日志中写记录。预留在写入相应记录时释放。如果截断记录与其他消息一起发送,截断记录预留也会释放。如果日志已满,协调器使用预留的空间写入明确的截断记录以释放日志空间。这种情况很少见,但为了确保活跃性,这是必要的操作。

4.2 Performance

对于我们的目标硬件,该协议和传统分布式提交协议相比具有一些优势。考虑具有复制的两阶段提交协议,例如Spanner [11]。 Spanner使用Paxos [24]来复制事务协调器及其参与者,这些参与者是存储事务读取或写入的数据的计算机。每个Paxos状态机在传统两阶段提交协议[19]中扮演单个计算机的角色。这需要2f + 1个副本以容忍f个故障,由于每个状态机操作至少需要2f + 1个往返消息,因此需要4P (2f + 1)个消息(P是事务中的参与者数量)。

在讨论的协议中,需要4P(2f+1)条消息来提供必要的容错能力,并在系统中的所有副本之间保持一致性。这里,P 表示事务中的参与者数量(即存储由事务读取或写入的数据的计算机),f 表示系统可以容忍的故障数量。在基于Paxos的协议中,需要2f+1个副本来容忍f个故障。4 这个系数来自于两阶段提交过程中的消息交换。在传统的两阶段提交协议中,每个操作至少需要2f+1个往返消息:

  1. 在第一阶段,事务协调器向每个参与者发送”准备(prepare)”消息,参与者回复,确认他们已准备好提交或中止。这需要(2f+1)条消息发送给参与者和(2f+1)条回复,总共2(2f+1)条消息。
  2. 在第二阶段,根据第一阶段的结果,事务协调器发送”提交(commit)”或”中止(abort)”消息。这同样需要(2f+1)条消息发送给参与者和(2f+1)条确认,总共又是2(2f+1)条消息。

两个阶段的消息总和是4P(2f+1),其中P是事务中的参与者数量。

FaRM 使用主备份复制而不是 Paxos 状态机复制。这将数据的副本数量减少到 f + 1,并减少了事务过程中传输的消息数量。协调器状态不会复制,协调器直接与主服务器和备份服务器进行通信,进一步降低延迟和消息计数。FaRM 由于复制所产生的开销最小:每个具有已写入对象备份的远程计算机仅需要一次 RDMA 写操作。只读参与者的备份在协议中根本不涉及。此外,通过 RDMA 读验证确保只读参与者的主服务器不执行任何 CPU 工作,并使用单向的 RDMA 写操作来进行 `COMMIT-PRIMARY` 和 `COMMIT-BACKUP` 记录,不仅去掉了这些记录需要等待远程 CPU回复的时间,并允许远程 CPU 的工作变得懒惰和批量处理。

使用单向 RDMA 写操作可以减少等待远程 CPU 的时间(相比于传统的流程,这里不需要对该记录的确认消息),并允许远程 CPU 的工作变得懒惰和批量处理,原因如下:

  1. 与传统的双向消息传递不同,单向 RDMA 写操作允许源节点直接将数据写入目标节点的内存中,而无需目标节点的 CPU 参与。因此,源节点无需等待目标节点确认消息已收到,从而降低了等待时间。

  2. 由于目标节点的 CPU 不需要立即执行任何操作来处理数据,因此它可以采用懒惰的方式处理这些数据。这意味着它可以在执行数据处理之前等待其他更紧急的任务完成,或者在处理大量数据时进行批量处理。这有助于提高系统的整体乐观并行性和吞吐量。

但是也有一定弊端,写入方不能马上确认消息收到,但是,可以采取以下方法之一来确保数据被成功写入:

  1. 定期检查:通过在目标节点上定期运行检查或轮询来检查是否已接收到数据。这样,目标节点可以在检测到数据后执行所需的操作。
  2. 异步通知:实现一种异步通知机制,当目标节点的 CPU 在处理数据时,会将接收到数据的确认发送回到源节点。这样,源节点可以在稍后的时间点知道数据已成功写入。

FaRM 的提交阶段使用 Pw(f + 3) 个单向 RDMA 写操作,其中 Pw 是作为事务写入对象的主节点的机器数量;以及 Pr 个单向 RDMA 读操作,其中 Pr 是从远程主节点读取但未写入的对象数量。读验证会在关键路径上添加两个单向 RDMA 延迟,但这是一个很好的权衡:在没有负载的情况下,新增的延迟只有几微秒,同时通过减少 CPU 开销可以获得更高的吞吐量和更低的负载下延迟。 FaRM 的设计利用了 RDMA(远程直接内存访问)技术来提高事务操作的性能。在提交阶段,读操作和写操作都借助 RDMA 来实现高效的一致性和数据同步。这种设计提供了在大规模分布式系统中执行事务的灵活性和可靠性,同时保持了较低的延迟和高吞吐量。

猜想:在 FaRM 分布式系统中,在事务的提交阶段,使用 Pw(f + 3)个单向 RDMA 写操作。原因如下:

  1. Pw:’Pw’ 是指持有由事务写入的对象的主副本的机器数量。
  2. (f + 1):对于事务写入的每个对象,FaRM 需要更新所有(f + 1)个副本以实现容错,如前所述。
  3. 额外的 2 个 RDMA 写操作:额外的 ‘+2’ RDMA写操作用于更新元数据,特别是事务状态以及可能的其他内部簿记信息。这些额外的写操作确保了系统保持一致性,并防止在发生故障时可能出现的异常或数据丢失。

因此,论文中提到的 Pw(f + 3)个单向 RDMA 写操作,结合了为每个主机器写入的(f + 1)个副本的要求,以及为维护元数据的额外 2 个 RDMA 写操作。不是必然正确,只是猜想。

5.Failure recovery

FaRM 通过复制提供持久性和高可用性。我们假设机器可能会崩溃,但在不丢失非易失性 DRAM 内容的情况下可以恢复。为了确保安全性,我们依赖有界的时钟漂移;为了保持活跃性,我们依赖最终有界的消息延迟。 FaRM 使用复制策略确保其在分布式环境中可以提供强大的持久性和高可用性。通过在多个节点上复制数据,FaRM 系统能够在某个节点发生故障时继续保持操作。假设服务器可能会崩溃,但在不丢失非易失性 DRAM 数据的情况下可以恢复。 为了确保系统安全和正常运行,FaRM 依赖以下两种方法:

  1. 有界时钟漂移:FaRM 使用物理时钟来同步节点上的操作。为了确保操作按照正确的顺序执行,并避免不一致,时钟漂移需要保持在一个有界范围内。这使得系统能够在分布式环境中实现正确的操作排序和协调。
  2. 最终有界的消息延迟:为了确保系统的活跃性和持续响应,FaRM 依赖最终有界的消息延迟。这意味着所有节点之间的通信都应该具有可预测的延迟范围,以便为客户端提供一致性和即时响应。 通过这些机制,FaRM 能够在处理故障和保持高可用性、高响应性方面提供可靠的性能。

我们为所有已提交的事务提供持久性,即使整个集群故障或丢失电源,也可以从存储在非易失性 DRAM 中的区域(regions)和日志中恢复已提交的状态。即使每个对象的最多 f 个副本丢失非易失性 DRAM 的内容,我们仍确保数据持久性。而且,当存在故障和网络分区时,只要有个分区里头包含了互相保持连接的大部分机器,也连着 Zookeeper 服务的大部分副本,且该分区至少有每个对象的一个副本,FaRM 那就还能维持可用性。

FaRM 故障恢复分为五个阶段,如下所述:故障检测、重配置、事务状态恢复、批量数据恢复和分配器状态恢复。

5.1 Failure detection

FaRM使用租约[18]来检测故障。除CM(配置管理器)以外的每台机器在CM处持有一个租约,同时CM也在每台其他机器处持有一个租约。任何租约的到期都会触发故障恢复。租约是通过三次握手(3-way handshake)授予的。每台机器向CM发送租约请求,CM回应一个同时作为机器的租约授予和CM的租约请求的消息。然后,机器回复一个给CM的租约授予。

FaRM的租约非常短,这是保证高可用性的关键。在重负载下,FaRM可以在90台机器的集群中使用5毫秒的租约,而不会出现误报。更大的集群可能需要两级层次结构,在最糟的情况下,这将使故障检测时间加倍。

在负载下实现短租约需要精细的实现。FaRM针对租约使用专用队列对(pairs),以避免当租约消息在共享队列中,且排在其他消息类型后时被延迟。使用可靠传输将需要在CM处为每台机器增加一个额外的队列对。这将导致由于网络接口卡队列对缓存[16]的容量不足而导致的性能下降。相反,租约管理器使用Infiniband发送和接收消息以及无连接的不可靠数据报传输,这仅需要在网络接口卡上增加一个额外的队列对的空间。默认情况下,租约续订操作每隔租约到期时间的1/5进行一次,以考虑潜在的消息丢失。

租约续订还需要及时在CPU上进行调度。FaRM使用一个专用的租约管理器线程,以最高的用户空间优先级运行(在Windows上为31)。租约管理器线程并未固定在任何硬件线程上,它使用中断而不是轮询,以避免饿死必须在每个硬件线程上定期运行的关键操作系统任务。这会使消息延迟增加几微秒,这对于租约来说不是问题。

这又涉及到OS的相关知识了:使用中断而非轮询可以避免这个问题,因为中断是一种事件驱动的机制。当硬件接收到租约管理器需要处理的事件(如租约到期)时,它会发送中断信号通知CPU。CPU在完成当前任务后会立即响应这个中断信号,处理租约管理器的事件。这种方式允许租约管理器和其他关键操作系统任务共享同一个硬件线程,又不会相互干扰。

而轮询需要租约管理器不断地主动检查事件是否发生,这可能会导致不能及时响应处理关键操作系统任务。因为轮询需要占用大量CPU时间来查询事件,使得关键操作系统任务可能无法得到足够的时间及资源来运行。因此,与轮询相比,中断机制可以更好地确保关键操作系统任务得到充足的资源和响应。

另外,我们不会将FaRM线程分配给每台机器上的两个硬件线程,以留给租约管理器使用:意思就是在每台机器上要留出两个硬件线程给租约管理器使用。我们的测量结果显示,租约管理器通常在这些硬件线程上运行,不会影响其他FaRM线程,但有时会被更高优先级的任务抢占,导致在其他硬件线程上运行。因此,将租约管理器固定在硬件线程上可能会在使用短租约时导致误报。

实际上,每台机器可能有多个硬件线程,我们选择保留其中的两个硬件线程供租约管理器使用,留出资源来处理租约相关事件。这里的关键在于确保租约管理器能够顺利完成工作,而不受其他FaRM线程的影响。

将租约管理器固定到硬件线程的做法可能会导致使用短租约时出现误报。原因在于:将租约管理器固定在硬件线程上可能使其无法灵活地调整资源以适应不同的操作系统任务,特别是在优先级更高的任务抢占资源时。另一方面,在未固定租约管理器的情况下,其可以更自由地在处理任务时在硬件线程之间切换。这样更有利于及时响应和处理租约事件,从而减少误报的可能性。

在这里,“误报”指的是租约管理器可能误判一个租约事件,例如错误地认为一个租约已经到期。这种情况可能发生在租约管理器无法立即处理租约相关的事件,例如租约续订请求,或者在处理这些事件时受到其他高优先级任务的干扰。如果租约管理器固定在特定的硬件线程上,它可能无法灵活地响应和应对这些状况,从而导致误报。

最后,在初始化过程中,我们为租约管理器预先分配所有所需内存,并在页面中锁定它使用的所有代码(即在缓冲池中给相关page上锁),以避免因内存管理引起的延迟。

5.2 Reconfiguration

重新配置协议将 FaRM 实例从一个配置移动到另一个配置。使用单向 RDMA 操作对于实现良好的性能非常重要,但它对重新配置协议提出了新的要求。例如,实现一致性的常用技术是使用租约[18]:服务器在回应访问对象的请求之前检查它们是否持有对象的租约。如果服务器从配置中被驱逐,系统保证它存储的对象在其租约到期(例如[7])之前不会被更改。FaRM在服务来自使用消息与系统通信的外部客户机的请求时使用此技术。但由于 FaRM 配置中的机器使用 RDMA 读操作在不涉及远程 CPU 的情况下读取对象,服务器 CPU 无法检查它是否持有租约。目前的 NIC 硬件不支持租约,目前尚不清楚未来是否会支持。

通过实施精确成员关系(precise member ship)[10],我们解决了这个问题。在故障之后,新配置中的所有机器必须在允许对象变更之前就达成一致。这使得 FaRM 可以在客户端而非服务器端进行检查。配置中的机器不会向不在其中的机器发出 RDMA 请求,并忽略不再在配置中的机器发出的 RDMA 读取回复和 RDMA 写入确认。

客户端总是知道配置变化何时完成,因为所有涉及新配置的机器需要在允许对象变更之前达成一致意见。在配置变化完成后,通常意味着新租约的开始。这意味着新配置中的所有成员都知道谁有权限访问和修改对象,确保系统中的数据一致性。

图 5 显示了一个示例的重新配置时间表,包括以下步骤:

image-20230628012211393

  1. Suspect。当配置管理器(CM)处的一个机器的租约过期时,CM会怀疑这台机器出现故障并启动重新配置。此时,它开始阻止所有外部客户端请求。如果非 CM 机器因租约到期怀疑 CM 故障,它首先要求少数“备份CM”中的一个启动重新配置(使用一致性哈希算法的CM的k个后继者)。如果在超时时间后,配置仍未发生变化,则尝试自行进行重新配置(代表是该机器自己的问题,不是CM的问题)。该设计避免了 CM 失效时过多的重新配置尝试。在所有情况下,启动重新配置的机器都将试图成为重新配置过程中的新 CM。

    在故障疑似阶段,当CM上的一台机器的租约过期时,CM会怀疑这台机器出现故障并启动重新配置。此时,CM 会阻止所有针对可能出现故障的这台机器的外部客户端请求。然而,此时其他正常工作的机器仍然可以接收外部客户端发送的请求。

  2. Probe。新 CM 向配置中的所有机器(疑似故障的机器除外)发起 RDMA 读操作。对于读取失败的任何机器,也将被认为是疑似故障。这些读取探测可以通过单次重新配置处理影响多台机器的相关故障,例如电源故障和交换机故障。新 CM 只有在收到大多数探测的响应时才会继续重新配置。这确保了如果网络分区,CM 不会处在较小分区中。

  3. Update configuration。在收到对探针的响应后,新的CM尝试将存储在Zookeeper中的配置数据更新为< c + 1, S, F, CMid >,其中 c 是当前配置标识符,S 是回复探测的机器集合,F 是机器到故障域的映射,CMid 是它自己的标识符。我们使用 Zookeeper znode 序列号来实现原子比较和交换,只有在当前配置仍为 c 时才会成功。这确保了即使有多台机器同时尝试从标识符为 c 的配置进行配置更改,也只有一台机器可以成功地将系统移动到标识符为 c+1 的配置(并成为 CM)。

  4. remap regions。接下来,新 CM 会重新分配之前映射到故障机器的区域(Regions),以将副本数恢复到 f + 1。它尝试在满足容量和故障独立性约束的情况下,平衡负载并满足应用程序指定的位置(本地性)提示。对于故障主机,它始终将幸存备份提升为新主机,以缩短恢复时间。如果它检测到某个Region丢失了所有副本,或者没有空间来重新复制区域,则会发出错误信号。

    在Farm中,Regions是数据存储与迁移的最基本单位。

  5. Send new configuration。在重新映射区域后,CM 向配置中的所有机器发送一个 NEW-CONFIG 消息,其中包含配置标识符、自身标识符、配置中其他机器的标识符以及区域到机器的所有新映射。如果 CM 发生变化,NEW-CONFIG 还会重置租约协议:此时它充当新 CM 向每台机器发出的租约请求。如果 CM 保持不变,则租约交换在重新配置期间继续进行,以便快速检测到其他故障。

  6. Apply new configuration。当一台机器收到具有比其本身配置标识符更大的 NEW-CONFIG 消息时,它会更新当前配置标识符和缓存的区域映射副本(该机器存储的regions会发生变化),并为分配给该机器的任何新区域副本分配空间。从这一点开始,它不会向不在配置中的机器发出新请求,拒绝这些机器的读取响应和写入确认。同时它还开始阻止来自外部客户端的请求。机器用 NEW-CONFIG-ACK 消息回复 CM。如果 CM 发生了变化,这既授予 CM 租约,也发出租约请求(NEW-CONFIG-ACK消息既表明了该机器认可了CM的存在,同时也表示该机器向CM请求一个租约,在上文中已经说明,CM与机器互相都有对方的租约)。

  7. Commit new configuration。一旦 CM 收到配置中所有机器的 NEW-CONFIG-ACK 消息,它会等待以确保在之前的配置中授予的、但是不再在当前新配置中的机器的所有租约都已经过期为止。然后,CM 向所有配置成员发送一个 `NEW-CONFIG-COMMIT`,该消息也充当租约授予消息。现在,所有成员解除之前阻止的外部客户端请求,并开始事务恢复。

5.3 Transaction state recovery

image-20230628023031514

FaRM使用分布在被事务修改的对象所在的副本上的日志恢复配置更改后的事务状态。这包括恢复被事务修改的对象副本的状态和协调器的状态,以决定事务的结果。图6展示了一个示例事务恢复时间线。通过将工作分布到集群中的线程和机器上,FaRM实现了快速恢复。排空(步骤2)是并行地为所有消息日志完成的。步骤1和步骤3-5对所有区域并行执行。步骤6-7是针对所有正在恢复的事务并行进行的。

  1. Block access to recovering regions。当一个区域的主服务器出现故障时,在重新配置过程中,其中一个备份服务器会被提升为新的主服务器。在更新该区域的所有事务都反映在新的主服务器上之前,我们不能允许访问该区域。我们通过阻塞对本地指针和对区域的RDMA引用的请求来实现这一点,直到步骤4为更新该区域的所有恢复事务获得了所有写锁。

  2. Drain logs。单向RDMA写操作也会影响事务恢复。在保持不同配置之间一致性上的常规方法是拒绝旧配置的消息。然而,FaRM不能采用这种方式,因为网卡会确认已写入事务日志的COMMIT-BACKUPCOMMIT-PRIMARY记录,而不管它们是在哪个配置中生成的。由于协调器在暴露更新并向应用程序报告成功之前只等待这些确认,因此在处理时,计算机不能总是拒绝来自前一个配置的记录。我们通过`Drain logs`来解决这个问题,以确保在恢复过程中处理所有相关记录:当计算机收到一个`NEW-CONFIG-COMMIT`消息时,它们处理日志中的所有记录。处理完成后,它们在一个名为`LastDrained`的变量中记录配置标识符。

    FaRM事务在提交开始时分配唯一标识符〈c, m, t, l〉,其中配置标识符c表示提交开始时的配置,协调器的计算机标识符m,协调器的线程标识符t,以及线程局部唯一标识符l。对于配置标识符小于或等于`LastDrained`的事务,其日志记录将被拒绝。

    “t”代表协调器的线程标识符,用以区分协调器内运行的不同线程。

    “l”是线程局部(unique to the thread)的唯一标识符,表示在特定线程内的每个事务都有一个独特标识,l用于区分在同一个线程内的不同的事务。

  3. Find recovering transactions。恢复事务是指提交阶段跨越配置更改的事务,且由于重新配置,已写对象的某个副本、已读对象的某个主副本或协调器发生了变化。在Drain logs期间,检查每个日志的每条日志记录中的事务标识符和更新的区域标识符列表,以确定恢复事务集合。只有正在恢复的事务才会在主副本和备份副本中进行事务恢复,而且协调器只会拒绝正在恢复的事务的硬件确认。

    所有计算机都必须就给定事务是恢复事务还是非恢复事务达成一致意见。我们通过在重新配置(5.2节内容)阶段的通信过程中携带一些额外的元数据来实现这一点。配置管理器(CM)作为probe读取的一部分来读取每台计算机上的LastDrained变量。

    对于每个自LastDrained以来映射发生变化的区域r,CM将在该计算机的NEW-CONFIG消息中发送两个配置标识符。这些配置标识符分别是LastPrimaryChange[r],即区域r的主副本发生变化的最后一个配置标识符;以及LastReplicaChange[r],即区域r的任何副本发生变化的最后一个配置标识符。一个在配置c-1开始提交的事务,除非满足以下条件,否则在配置c中是恢复事务:对于包含事务修改对象的所有区域r,LastReplicaChange[r] < c,对于事务读取对象的所有区域r’,LastPrimaryChange[r'] < c,并且协调器未从配置c中移除。

    恢复事务的记录可能分布在不同主副本和备份副本的日志中,这些副本由事务更新。每个区域的备份副本都会给主副本发送一个NEED-RECOVERY消息,消息会包含配置标识符、区域标识符以及更新了该区域的恢复事务的标识符(即需要恢复的事务的标识符)。

  4. Lock Recovery。每个区域的主副本都会等待本地计算机日志排空(即等待Drain logs阶段结束),并从每个备份副本接收NEED-RECOVERY消息,以构建影响该区域的完整恢复事务集合。然后,它将这些事务按标识符分片到各个线程上,使每个线程t恢复具有协调器线程标识符t的事务状态。同时,主副本的线程并行地从备份副本获取本地未存储的任何事务日志记录,然后锁定恢复事务修改的对象。

    当一个区域的Lock Recovery完成时,该区域处于active状态,本地和远程协调器可以获得本地指针和RDMA引用,这允许它们在随后的恢复步骤中读取对象并向该区域提交更新。

    对于划线句:这里的线程t指的是主副本里的线程。线程t与协调器的线程t之间存在一定的关系,因为它们都负责处理具有相同线程标识符t的事务。这种分配方法使得协调器上的线程t和主副本上的线程t在事务处理方面保持一致性。

    线程t是主副本中的一个线程,负责处理具有特定协调器线程标识符t的事务。通过这种方式,每个线程负责处理一部分事务,使恢复过程更加高效。然后,主副本与协调器线程通过这些线程标识符来协调恢复工作。 在论文中,主副本可能与协调器线程关于事务的状态保持一致。

    线程符号级同步带给我们的优势主要包括:

    1. 更高效的并行性能:通过将具有相同线程标识符的事务分配给对应的主副本线程,系统能在恢复事务时实现高效并行。这样可以缩短事务处理时间,提高整体性能。
    2. 更简便的状态协调:主副本与协调器中的线程具有相同状态,可以简化事务恢复过程,使得主副本能更容易地与协调器中的线程进行协同工作。
    3. 数据一致性:通过确保主副本和具有相同线程标识符的协调器线程保持同步状态,可确保跨多个区域和副本的数据一致性。

    选择具有相同线程标识符的主副本线程与协调器线程并非随意选择,而是根据事务的处理需求来实现恰当分配。这样可以确保在事务恢复过程中各个线程的符号级同步,从而带来上述优点。 因此,在实际场景中,通常不能随意选择线程来执行此操作,而是应根据系统设计把对应的事务分配给相同线程标识符的线程。这将有助于实现更高效的事务处理和更好的数据一致性。

  5. Replicate log records。主节点中的线程通过为缺失的事务向备份节点发送 REPLICATE-TX-STATE 消息来获取自己缺失的日志记录,而正常情况下是主节点向备份节点发送自己的日志记录。该消息包含区域标识符、当前配置标识符以及与 LOCK 记录相同的数据。这样一来,备份节点能够保持与主节点的数据一致性。在这种机制下,备份节点可以及时处理事务,并确保在主节点发生故障时仍能提供必要的数据。

    这里的逻辑是,主节点线程从备份节点请求与其缺失事务对应的日志。主节点线程会检查自己是否缺失某个事务的日志记录,如果缺失,它就会向备份节点发送 REPLICATE-TX-STATE 消息。随后,备份节点会将相应的日志记录发送回主节点。

    通常情况下,主节点会发送事务日志记录到备份节点,以确保数据的一致性。然而,在某些情况下,例如主节点发生崩溃重启时,主节点可能缺失某些事务日志。这时,主节点线程就会根据需要从备份节点获取缺失的日志记录。整个过程旨在保证主备节点间的日志记录一致性和数据可靠性。

  6. vote。对于一个处于恢复阶段的事务,其协调器会根据事务更新的每个区域的主节点发送的投票来决定是提交还是中止事务。FaRM 使用一致性哈希来确定事务的协调器,确保所有主节点独立地就恢复事务的协调器的身份达成一致。如果协调器所运行的机器仍处于当前的配置中,则协调器不会更改。但当协调器发生故障时,协调其恢复事务的职责会在集群中的机器上分散开来。

    在主节点中的线程会针对修改了区域的每一个处于恢复状态的事务,向协调器中的对等线程发送 RECOVERY-VOTE 消息。如果任何副本看到 COMMIT-PRIMARYCOMMIT-RECOVERY,则投票为 commit-primary。否则,如果任何副本看到 COMMIT-BACKUP 并且没有看到 ABORT-RECOVERY,则投票为 commit-backup。再者,如果任何副本看到 LOCK 记录且没有看到 ABORT-RECOVERY,则投票为 lock。其它情况下,投票为 abort。投票消息包括配置标识符、区域标识符、事务标识符以及事务修改的区域标识符列表。

    这一投票过程确保了对于正在恢复的事务,主节点内的线程能够将投票结果(总计了replica的投票结果得出的结果)传递给协调器。协调器依据主节点线程的投票结果来决定事务的最终状态,例如提交或中止。这样可以保证在故障恢复期间,整个系统对事务的处理更加合理和一致。

    一些主节点可能不会针对某个事务发起投票,原因可能是它们从未收到过该事务的日志记录,或者它们已经截断了该事务的日志记录。对于在超时阶段(设定为250微秒)内尚未投票的主节点,协调器会发送明确的投票请求。REQUEST-VOTE 消息包括配置标识符、区域标识符和事务标识符。对于拥有该事务日志记录的主节点,在等待该事务的Replicate log records阶段完成之后,会像之前那样进行投票。

    这一机制确保了在某些主节点未能参与投票的情况下,协调器能够通过发送明确的投票请求来尝试获取它们的投票。这有助于在故障恢复过程中更好地达成关于事务处理的共识,从而使系统更加稳定和一致。同时,这也解决了因为日志丢失或截断导致投票无法进行的问题。

    对于没有任何事务日志记录的主节点,如果事务已被截断,则投票截断(truncated),若事务尚未被截断,则投票未知(unknown)。为了确定某个事务是否已被截断,每个线程维护一个已从日志中截断记录的事务标识符集合。通过对未截断事务标识符使用一个下界,可以使该集合保持紧凑。下界是根据每个协调器的下界更新的,协调器上的下界附加在协调器消息上,并在重新配置期间进行更新。

    这种投票机制确保了即使主节点没有事务的日志记录,它们也能够根据事务的截断状态进行合理投票。通过维护截断事务标识符集合以及相应的下界,主节点线程可以更准确地确定事务的状态,从而有效参与到投票过程中,使系统在故障恢复阶段能更好地达成共识。

    此处的“每个线程”指的是主节点中的线程。每个线程都维护一个已被截断记录的事务标识符集合,这样它们可以跟踪各自所处理的事务的状态。 至于“事务标识符集合”,这里的集合并不是一个全局视图,而仅限于此主节点。这意味着,在同一个主节点中的各线程可以共享有关事务的信息,但这些信息仅在当前主节点内可见。

    在分布式系统中,集群的其他主节点也可以维护自己的事务标识符集合,用于跟踪在各自节点中处理的事务状态。这有助于集群的每个节点可以独立掌握其事务处理状况,从而在需要时进行协调和投票。

  7. decide。如果事务协调器从任何区域收到 commit-primary 投票,它会决定提交该事务。否则,协调器将等待所有区域的投票,如果至少有一个区域投票为 commit-backup 并且由事务修改的所有其他区域投票为 lockcommit-backuptruncated,则提交。否则,它会决定中止事务。接着,协调器会发送 COMMIT-RECOVERYABORT-RECOVERY 消息到所有参与副本。两种消息都包括配置标识符和事务标识符。在主节点上,收到 COMMIT-RECOVERY 消息后的处理与收到 COMMIT-PRIMARY 类似;在备份节点上,收到的处理类似于 COMMIT-BACKUP。ABORT-RECOVERY 消息的处理与 ABORT 类似。在协调器收到来自所有主节点和备份节点的确认后,它会发送一个 TRUNCATE-RECOVERY 消息。

    通过这一决策与通信过程,协调器可以根据各区域的投票结果来决定事务的命运:提交或中止。最终,协调器还会负责通知各参与节点执行相应的操作,确保数据的一致性和正确性。在整个过程中,协调器不断与主备节点进行信息交换,以便在集群中实时更新事务状态。

5.3.1 Correctness

接下来我们将简要阐述事务恢复的不同阶段是如何确保严格可序列化的。关键思路是恢复过程会保留以前已提交或中止事务的结果。我们认为当满足以下条件之一时,事务为已提交:主节点暴露事务修改或协调器通知应用程序该事务已提交。当协调器发送一个中止消息或通知应用程序事务已中止时,事务被视为已中止。对于尚未决定结果的事务,恢复过程可能会提交或中止事务,但它确保从额外故障中的任何恢复都会保留结果。

未能恢复的事务(第3步)的结果是使用正常情况协议(第4节)来决定的。所以我们不再进一步讨论这些事务。

注意这里和下面所提到的第n步指的都是在5.3节(即上面阐述的事务恢复的步骤)中的内容。

对于已提交的恢复事务的日志记录,在日志排空(第2步)之前或期间,保证已被处理和接受。这是因为主副本仅在处理COMMIT-PRIMARY记录后才会暴露修改。如果协调器通知了应用程序,那么在收到NEW-CONFIG之前,它必须已经收到所有COMMIT-BACKUP记录的硬件确认,以及至少一个COMMIT-PRIMARY记录的硬件确认(因为在更改配置后将忽略确认)。因此,由于新的配置至少包括每个区域的一个副本,至少有一个区域的至少一个副本将处理COMMIT-PRIMARYCOMMIT-BACKUP记录,每个其他区域的至少一个副本将处理COMMIT-PRIMARYCOMMIT-BACKUPLOCK记录。

第3步和第4步确保被事务修改的区域的主副本看到这些记录(除非它们已被截断)。它们将这些记录复制到备份副本(第5步),以保证即使出现后续故障,投票也会产生相同的结果。然后,主副本根据所看到的记录向协调者发送投票(第6步)。

决策(step7)确保协调者决定提交任何之前已经提交的事务。如果有任何副本截断了事务记录,所有主副本都会投票提交主副本(commit-primary)、提交备份副本(commit-backup)或截断。至少有一个主副本发送的投票不会是截断,因为否则事务将无法恢复。如果没有副本截断事务记录,那么至少有一个主副本将投票提交主副本(commit-primary)或提交备份副本(commit-backup),其他的将投票提交主副本、提交备份副本或锁定。同样,如果事务之前被中止,协调者将决定中止事务,因为在这种情况下,要么没有提交主副本(commit-primary)或提交备份副本(commit-backup)记录,要么所有副本都收到了中止恢复(ABORT-RECOVERY)。

阻止访问恢复中的区域(第1步)和锁定恢复(第4步)确保,在恢复事务提交或中止之前,其他操作不能访问被这些恢复事务所修改的对象。

5.3.2 Performance

FaRM使用了多种优化技术来实现快速的故障恢复。识别恢复事务将恢复工作限定为那些仅受重新配置影响的事务和区域,当大型集群中的单个机器发生故障时,这可能仅占总数的一小部分。我们的结果表明,这可以将要恢复的事务数量减少一个数量级。恢复工作本身是跨区域、机器和线程并行进行的。在lock recovery阶段后立即使区域可用可以提高前台性能,因为访问这些区域的新事务不会长时间阻塞。具体来说,它们不需要等待这些区域的新副本更新,而等待这些区域的新副本更新需要通过网络大量移动数据,等待这些会花费大量的时间。

lock recovery后主副本已经可用,剩下工作就是更新备份副本并提交事务。

5.4 Recovering data

FaRM必须对某个区域的新备份副本进行数据恢复(重新复制),以确保它将来能够容忍f个副本故障。数据恢复并非恢复正常操作所必需,因此我们将其延迟到所有区域变为活动状态,以尽量减小对延迟关键的lock recovery阶段的影响。当所有主副本区域变为活动状态时,每台机器都向配置管理器(CM)发送一个REGIONS-ACTIVE消息。在收到所有REGIONS-ACTIVE消息后,CM向配置中的所有机器发送一个ALL-REGIONS-ACTIVE消息。此时,FaRM开始为新备份副本进行数据恢复,与前台操作并行进行。

对于一个区域的新备份,它最初会为已分配并归零的本地区域副本。它将区域划分给多个工作线程,并行进行恢复。每个线程发出单向RDMA操作,一次从主副本读取一个数据块。我们目前使用的是8 KB的数据块,这个大小足够高效地使用网络,但又不至于影响正常操作。为了降低对前台性能的影响,恢复操作按一定节奏进行:即在上一次读取开始后的一个间隔(设定为4ms)内的随机时刻启动下一次读取。

在将每个已恢复的对象复制到备份副本之前,必须对其进行检查。如果对象的版本高于本地版本,备份副本会使用比较并交换(compare-and-swap)对本地版本进行锁定,更新对象状态,然后解锁。否则,该对象已经被或正在被创建大于或等于已恢复版本的事务更新,这种情况下,已恢复状态将不会被应用。

5.5 Recovering allocator state

FaRM分配器将区域(regions)分割成数据块(1 MB),这些数据块(blocks)作为存储空间(slabs)分配小对象(small objects)。它保留两个元数据:包含对象大小的数据块标头(header)和存储空间空闲列表。当分配新数据块时,数据块头会复制到备份副本中。这确保在发生故障后,新主副本可以访问它们。由于数据块头用于数据恢复,新的主副本会在收到NEW-CONFIG-COMMIT后立即将其发送给所有备份副本。这样可以避免在旧主副本在复制数据块头时发生故障导致的任何不一致问题。

到此,FaRM的存储数据结构应该已经明了:一个region由若干blocks组成,每个block又含有多个存储空间(slabs)。区域作为复制单位,负责跨机器的数据分布和冗余管理,而数据块和存储空间则用于区域内的内存分配和对象管理。

存储空间(slabs)的空闲列表仅保留在主副本上,以减少对象分配的开销。每个对象在其header中都有一个位,由allocation操作设置并在事务执行过程中由free操作清除。对象状态的这种变化在事务提交过程中会按照第4部分的描述进行复制。故障发生后,通过并行扫描整个区域的对象(因为机器上的所有线程都会参与这个扫描过程),从而在新主副本上恢复空闲列表。为了尽量减少对事务锁恢复的影响,在收到ALL-REGIONS-ACTIVE后,分配器的恢复操作才开始,为了尽量减少对前台工作的影响,它会以每100 μs 扫描100个对象的速度进行。对象的释放操作被排队,直到一个存储空间的空闲列表恢复。

6. Evaluation

测试部分不看,后续有需要再看。

7. Related work

根据我们的了解,FaRM是第一个同时提供高可用性、高吞吐量、低延迟和严格串行化的系统。在之前的工作[16]中,我们概述了一个早期版本的FaRM,它将数据记录到SSD以保证持久性和可用性,但我们并未描述从故障中恢复的过程。本文描述了一种新的快速恢复协议以及一种优化的事务和复制协议,该协议大大减少了消息传输,并利用NVRAM避免将数据记录到SSD上。与[16]中描述的事务协议相比,优化后的协议发送了高达44%更少的消息,并在验证阶段将传统消息替换为单向的RDMA读取操作。[16]中的工作仅评估了在没有故障的情况下使用YCSB基准测试的单键事务性能。在这里,我们使用TATP和TPC-C基准测试评估事务在有无故障情况下的性能。

RAMCloud[33, 34]是一个键值存储,它将数据的单个副本存储在内存中,并使用分布式日志确保数据持久性。然而,它不支持多对象事务。在发生故障时,RAMCloud在多台机器上并行恢复,而在此期间(可能需要几秒钟),故障机器上的数据将不可用。与之相比,FaRM支持事务,在故障后数十毫秒内使数据可用,并且每台机器的吞吐量高一个数量级。

在第4节中讨论过的Spanner[11]提供了严格的串行化,但未针对RDMA优化性能。它使用2f+1个副本,而FaRM使用f+1个,且FaRM提交时发送的消息更少。Sinfonia[8]提供了一个共享地址空间,其中可串行事务采用两阶段提交实现,并在专用情况下将读取操作捆绑到两阶段提交过程中。然而,FaRM提供了针对RDMA优化的通用分布式事务。

HERD[23]是一个基于RDMA的内存键值存储系统,在异构环境下提供每台服务器的高性能,客户端与服务器运行在不同的机器上。它使用RDMA写和发送/接收消息,但不使用RDMA读。[23]的作者证明了在**异构环境**下,单向RDMA读性能不如无可靠性的专用RPC实现。我们的结果在**对称环境**下使用可靠通信,每台机器既是客户端又是服务器。这使得我们能够利用本地性,因为访问本地DRAM比使用RDMA访问远程DRAM更快[16]。 Pilaf[31]是一个使用RDMA读取的键值存储。然而Pilaf和HERD都不支持事务。HERD没有容错能力,而Pilaf则通过将日志记录到本地磁盘来保证数据持久性,但不具备高可用性。

在分布式系统的环境中,“非对称”与“对称”是指系统内的服务器和客户端的角色和配置。

非对称:在非对称分布式系统中,服务器和客户端的角色是明确区分的。服务器负责处理数据存储和处理任务,而客户端负责向服务器发送请求并接收响应。这种设置使服务器针对特定任务进行优化,而客户端则专注于用户交互或其他应用特定任务。以HERD为例,它是一个基于RDMA的非对称内存键值存储系统,其中客户端和服务器分别运行在不同的机器上。

对称:在对称分布式系统中,系统中的每个节点既可以充当服务器,也可以充当客户端,两者之间没有固定的区分。这种设计允许所有机器更有效地使用资源,并从本地性中受益,通常导致更好的性能。例如FaRM,它采用了对称配置,每台机器既是客户端又是服务器,从而充分利用本地性以实现更高的性能。

Silo[39, 40]是一个单机主内存数据库,通过将日志记录到持久性存储来实现数据持久化。它将已提交的事务批量写入存储中,以实现高吞吐量。故障恢复涉及从存储中读取检查点和日志记录。Silo的存储是本地的,因此当机器发生故障时,可用性会丧失。相比之下,FaRM是分布式的,并使用NVRAM中的复制来实现持久性和高可用性。对于更大的数据库,FaRM在故障后恢复峰值吞吐量的速度比Silo快两个数量级以上。通过扩展和使用NVRAM中的复制,FaRM还实现了比Silo更高的吞吐量和更低的延迟。 Hekaton[14, 26]同样是一个单机主内存数据库,不支持扩展或分布式事务。拥有3台机器的FaRM与Hekaton的性能相当,拥有90台机器的FaRM吞吐量是Hekaton的33倍。

8.Conclusion

事务使分布式系统的编程变得更加容易,但许多系统为了提高可用性和性能而避免事务或降低其一致性。FaRM 是一种面向现代数据中心的分布式主内存计算平台,提供高吞吐量、低延迟和高可用性的严格串行化事务。实现这一目标的关键是设计新的事务、复制和恢复协议,从原理上充分利用支持RDMA的商用网络,并采用一种新的、经济实惠的方法来提供非易失性DRAM。实验结果表明,与现有技术水平的内存数据库相比,FaRM可以提供显著更高的吞吐量和更低的延迟。此外,FaRM可以在不到50毫秒的时间内从机器故障中恢复并达到峰值吞吐量,使得应用程序在失效时透明。

back.