FaRM论文总结——Lec14

前言

FaRM是支持分布式事务的的高性能内存存储系统,实现了速度很快的分布式事务。为了使用RDMA并且对事务进行支持,使用了乐观锁机制。通过数据分片和硬件特点,解决了**网络(RDMA)存储(DRAM)**的瓶颈。

一、FaRM简介

1.FaRM系统:一个高可用的支持分布式事务的的高性能内存存储系统。

2.大名鼎鼎的CAP定理声明:一致性(Consistent)、可用性(Available)、网络分区(Partition)三者形成“不可能三角”,只能择其二成立。此外,我们通常还需要考虑分布式系统的性能(Performance)
实践中,现有系统通常通过降低一致性约束来提高性能。比如:

  • 大多数实现了ACID事务特性的系统是单机系统。比如Mysql。
  • 大多数支持分片的系统是基于更弱的一致性保障——最终一致性。比如Redis。
  • 大多数支持分片的系统不支持事务。比如Dynamo等大多数系统。

”小孩子才做取舍,大人我都要“,今天介绍的Farm系统独树一帜,宣称在CAP方面毫不妥协。他的论文标题是《No compromises: distributed transactions with consistency, availability, and performance》。

3.分布式事务的一个研究型系统(不是已经落地的工程实现)。

4.乐观锁并发控制是它所使用的技术中我们最感兴趣的一项。
它是6.824课程中事务和Replication以及数据分片这块中最后一篇paper,这依然是一个开放的研究领域。在性能和一致性方面,人们想要做出一定的取舍,他们仍想做到更好,这些新的具备RDMA能力的NIC拥有巨大的性能潜力,从而激发了人们写出了这篇paper。

5.与spanner不同之处在与 spanner可以跨数据中心,论文中的FaRM系统只能在单数据中心,但是性能非常高。

二、FaRM与Spanner对比

1.在事务方面,它们非常相似它们俩都使用了复制和两阶段提交

2.Spanner这个系统已经被广泛使用很长一段时间了,它的主要重心在于Geographic replication(地理区域级别的复制)。
为了解决跨远程进行二阶段提交上的时间问题,其中最具创新的地方在于,对于只读事务,通过使用同步时间,它拥有一种特别的优化路线。

3.它是一种研究原型,它并不是一个已经完善的产品,其目标是在于探索这些新的RDMA高速网络硬件的潜力。
它所假设的情况是,所有的replica都在同一个数据中心,它并不会试着去解决Spanner所要解决的问题。比如,如果整个数据中心都挂掉了,那我还能拿到我的数据吗。它的容错能力的范围可能是针对单个服务器的崩溃
它使用了RDMA这项技术,但这已经严重限制了设计选项,因为这个原因FaRM强制使用了乐观锁并发控制,他们所获得的性能要远比Spanner高得多
(注:RDMA可以绕过CPU,正因如此不能使用需要CPU处理的方式)

4.FaRM的性能比Spanner高出太多(约100倍),但它并不是用于解决Geographic replication的。

5.Spanner和FaRM针对的是不同瓶颈,在Spanner中,人们所担心的主要瓶颈在于卫星信号传播上的延迟以及数据中心间的网络延迟。然而,在FaRM中,该设计中存在的主要瓶颈在于服务器上的CPU时间,因为他们希望通过将所有的replica放在同一个数据中心中,以此来消除卫星信号和网络传播所带来的延迟。

三、FaRM的特性

1.FaRM设置

(1)FaRM中的设置是这样的,将所有的replica放在同一个数据中心中运行,配置管理器会去决定每个数据分片中哪台服务器是primary,哪台服务器是backup,他们使用了Zookeeper来帮助他们实现配置管理器。

(2)他们根据key将数据进行分片并分散到一堆primary-backup pair上去,每个数据分片对应了一个primary服务器和一个backup服务器(如下图的p1 b1和p2 b2),这些replica并不是由Paxos之类的东西来进行维护的,相反,当有一处发生了改变,那么该数据所属的所有replica都会进行更新,如果你要去读取数据,那么你始终得从primary处读取数据
使用这种replication方式的理由当然就是为了具备容错能力,只要给定数据分片中的一个replica是可用的,那么这个数据分片就是可用的。如果它们有f+1个replica,那么对于这个数据分片来说,它们就可以容忍f个replica发生故障。

(3)FaRM还运行着事务协调器CM,为了方便起见,我们可以将事务协调器当做独立的client来看待(事实上,在他们的实验中,他们将事务协调器和FaRM的存储服务器放在同一台机器上运行),这些client会去执行事务,这些事务需要去对保存在数据分片上的数据对象进行读写,此外,它们还扮演了两阶段提交中事务协调器的角色,这就是它的基本设置,这也是paper中他们获得高性能的方式。

2.FaRM获得高性能的技术方式

FaRM获得高性能的技术方式如下图所示:
在这里插入图片描述
(1)高性能的同时它们依然可以对事务进行处理,他们获得高性能的其中一种方式就是对数据进行分片并行获得翻倍的性能。(上图中的sharding)

(2)另一个技巧是,在FaRM中并不需要将数据持久化到磁盘,而是将数据都放在了RAM中,事务会在RAM中对该数据对象进行修改速度就会变得非常快。
他们获得高性能的这种方式缺点是需要去容忍供电故障。所以他们使用了一种更好的NVRAM(非易失性RAM)方案来解决RAM因供电故障导致数据全丢的情况。它在每个机架上都放了一个大电池,来为服务器进行供电它将这些电池组成了一个供电系统。FaRM使用的还是传统的RAM,但本质上来讲,使用后备电源来做到RAM的非易失性,使得数据能够从供电故障中存活下来,但这只对供电故障有效。其他原因造成的系统崩溃会丢失它RAM中的所有内容,并且它没法恢复这些数据。这就是为什么除了使用NVRAM以外,FaRm还得为每个数据分片建立多个replica的原因所在了。

(3)他们所使用的另一个技巧就是使用了RDMA技术,接简单来讲就是,在不对服务器发出中断信号的情况下,他们通过网络接口卡(NIC)收数据包并通过指令直接对服务器内存中的数据进行读写。
他们所玩的这种技巧通常叫做kernel bypass,在不涉及内核的情况下,应用层代码可以直接访问网络接口卡,设计人员花了大量的时间和精力来消除服务器间数据交换的性能瓶颈。(下文会详细讲解RDMA技术及演进)

(4)总结:高性能的原因除数据分片外主要来自于两个不同的硬件:

  • 存储使用非易失性的DRAM(外挂了电源,机器断电时,外挂电源生效,DRAM中的数据全部落盘后关机,仅能对断点进行容错)
  • 网络使用RDMA(remote direct memory access)(可以按照DMA去理解,即读取远端数据可以不走复杂的网络协议,直接读取远端机器中内存数据(绕过内核))

注:RDMA网络协议允许用户程序绕过操作系统内核直接进行网络通信。这样既避免了用户空间到系统空间的复制开销,也可以省去进入内核处理的开销,极大地降低了网络时延,并且提高了吞吐量。此外, RDMA技术还提供了新的网络原语,网卡可以绕过处理器直接处理对服务器内存的读写请求。网卡提供了对远程内存的读(read)、写(write)和原子(atomics)操作,可以极大地提升远程服务器的CPU使用效率。
(参考:https://www.jianshu.com/p/fd94a2e84af6)

DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。

2.FaRM设计原则

FaRM通过以上两个硬件特点,解决了网络(RDMA)存储(DRAM)的瓶颈,此时CPU的瓶颈出现了,FaRM在设计上遵循下面3条原则:

  • 减少消息数量:减少消息数量的原因是打包、拆包和分派中所涉及的所有CPU
  • 使用RDMA进行数据的存取而不是消息,即使用one-side RDMA读写代替消息
  • 高效利用并发

3.one-side RDMA

(1)RDMA代表远程直接内存访问。 one-side RDMA是RDMA支持的最快的通信方法,它绕过CPU可以单向直接访问另一台计算机的内存。
在这里插入图片描述
one-side RDMA可以直接访问远程数据,而完全不涉及远程CPU,并且对本地CPU的要求最低。(单向RDMA请求的处理在NIC中进行,NIC是网络接口控制器)
(2)传统架构和FaRM采用的方案
不同服务器上应用程序间RPC数据包的交换的传统架构
这里先介绍下传统架构,方便我们理解为什么FaRM所使用的这种方案更为高效
一台服务器想去发送一条RPC消息,应用程序APP在用户态空间中运行着,为了发送数据应用程序得调用内核中的系统调用,我们还需要通过一些内核中的软件在网络中发送数据。通常有一个叫做Socket层的东西socket buffer,它的作用是对数据进行缓存,这涉及到了比较花费时间的复制数据操作。还会有一个复杂的TCP协议栈、序列化,涉及到处理所有关于重传,checksum以及flow control的所有相关事情。在底部有一个叫做网络接口卡(NIC Network Interface Card)的硬件,内核可以对它上面的一堆寄存器进行通信和配置,它上面还拥有能够通过电缆将bit信息发送到网络上的硬件。内核中的网络接口卡驱动通过网络接口卡直接往主机内存读取和写入数据包,这里会有一个队列里面放着网络接口卡通过DMA访问内存时所拿到的(目标端服务器的)数据,并等待内核去读取这个队列中的数据包,这里还有一个用于对外发送数据的数据包队列。这样网络接口卡可以尽可能方便地将数据发送出去,当你想去发送一条RPC请求,应用程序会一步步往下走,网络接口卡会通过网线发送bit信息
总结: 应用程序(用户空间) ——> socket buffer(系统空间,缓存app的数据)——> TCP协议——>NIC驱动(下图中的两个队列:通过DMA访问内存拿到数据的数据包;对外发送数据的数据包)——> NIC(发送bit信息)。
接着,在另外一边会有一个相反的栈。有一个网络接口卡,接着,网络接口卡会发送一个中断信号给内核,内核会通过驱动程序将数据包发送给TCP协议进行处理,然后这些数据包会被写入buffer,并等待应用程序在某个时间点去读取这些数据,通过系统调用将内核buffer中的数据拷贝到用户态空间里,这里面存在着大量的软件、大量的处理以及很多十分消耗CPU资源的操作——调用,interrupt中断以及复制数据。
在这里插入图片描述
②FaRm并没有使用这种方案,相反FaRm通过两种思路来减少推送数据包的成本,第一种方案叫做kernel bypass:与其让应用程序通过调用复杂的内核代码来发送所有数据,相反,通过对内核保护机制进行配置,以此来让应用程序直接访问网络接口卡
当使用这种DMA以及kernelBypass方案时,网络接口卡通过DMA能直接访问应用程序内存,在不需要内核参与的情况下,应用程序可以直接看到那些到达的字节信息。当需要发送数据时,应用程序可以去创建一些队列,网络接口卡可以直接通过DMA来读取数据,并通过网线将数据发送出去。所以现在我们就已经消除了所有涉及网络的内核代码调用,即内核不会参与这些操作,这里也没有系统调用、interrupt中断,应用程序可以直接对内存进行读写,网络接口卡可以直接看到这些内容,对于另一边也是如此。
在这里插入图片描述
正常情况下,应用程序代码是不能直接对设备进行任何操作。所以我们得对Linux进行修改,以此让内核将硬件访问的权限委派给应用程序,这依赖于相当智能的NIC,事实上现代的NIC知道该如何和多个不同的队列进行通信,所以你可以运行多个应用程序,并且每个应用程序都有它自己的一组队列,NIC知道该如何跟它们进行通信,这确实需要修改很多东西。
总之,需要内核授权给应用程序之间访问硬件NIC。而且依赖NIC实现多个应用程序数据队列访问的功能。
注:这种方法有些类似Java的NIO,参考另一篇博客(一-6-(2))
③第一种是去使用KernelBypass这种方案。第二种是一种更为聪明的办法——RDMA,远程直接内存访问,这是一种特殊的网络接口卡,它支持RDMA。
现在发送方和接收方都得有支持RDMA的NIC。下图中它们是通过网线连接的,事实上,这里会有一台交换机,通过它可以与任意服务器进行通信有很多不同的服务器都和它连接在一起。
现在在源主机这块运行着一个应用程序,(源机)可以通过RDMA系统发送一条特殊的消息来告诉网络接口卡让它直接对目标应用程序地址空间中的内存直接进行读写操作,所以网络接口控制器上的硬件和软件会对目标应用程序的内存直接进行读写操作。我们会将一个请求发送到下图OST APP处的箭头处,它会引起读操作或者写操作。然后目标应用程序会将响应结果返回给源应用程序的incoming queue中。重点是,目标服务器上的CPU和应用程序对于刚刚所做的读操作或者写操作并不知情,读操作或者写操作完全是在网络接口卡的固件中执行的。所以这里不会有interrupt中断发生,应用程序不用考虑请求或者响应之类的事情,目标主机上的网络接口卡只需要去对应用程序内存进行读取或写入操作,然后将结果返回给源应用程序即可。
这种做法的开销会很低,你所要做的是对目标应用程序RAM中的数据进行读写,它要通过使用这种很神奇的kernel bypass networking,在进行简单的读和写的时候,远远比通过普通的RPC调用来发送信息的方式快得多。
在这里插入图片描述
当应用程序通过RDMA对另一个机器上内存中的数据进行读写时,使用的是one-sided RDMA
FaRM有时候会通过one-sided RDMA来直接读取数据,但FaRM有时候使用RDMA是用来给目标对象的incoming message queue追加消息的(如上图SRC APP右侧箭头处),因为这里没有人发出interrupt信号,所以目标机器会对内存中所接收到的消息进行定期检查,来看看其他人在最近是否有给我发送消息。
(3)使用RDMA是为了进行读和写,有时候,FaRM也会使用RDMA去给另一台服务器的日志追加消息给一个消息队列或者日志条目。
一个非常好的应用就是通过one-sided RDMA来直接对保存在数据库服务器内存中的记录进行读和写,如果我们不需要去和数据库服务器的CPU或者软件进行通信,但通过使用one-sided RDMA能在5毫秒内就可以拿到我们需要的数据,并对其进行读写。

(4)我们能否只使用one-sided RDMA来实现事务
即在不发送那些必须由服务器软件进行解释的消息的情况下,我们只使用RDMA来对服务器中的数据进行读或者写?
FaRM对于这个问题的答案是No,为什么我们无法单纯地靠one-sided RDMA来做到这点?在事务系统中使用RDMA的难题在于replication以及数据分片,我们所面临的挑战就是该如何将事务、数据分片以及replication结合在一起,在我们目前为止看到的所有用来处理事务复制的协议,服务器都得参与进来,帮助client对数据进行读取和写入。
服务器需要弄清楚最近更新的数据是否已被提交,这可以防止client读到那些被锁住或者还未被提交的数据。这意味着,在没有一些更好办法的情况下,RDMA或者one-sided RDMA似乎是无法直接兼容事务和replication的,当FaRM使用one-sided RDMA去直接读取数据库中的数据时,它是没法使用one-sided RDMA来对该数据进行修改的。所以,这就会让我们需要使用乐观锁并发控制

4.乐观并发控制

(1)为了让FaRM能够使用RDMA并且对事务进行支持,他们所使用的主要手段就是乐观锁并发控制
(2)并发控制方案分两大类:悲观、乐观
悲观:锁——2PL(两阶段锁)
在这个锁方案中,数据得被锁住,有人得去跟踪谁持有该数据所对应的锁,什么时候该锁被释放等等,这是RDMA使用过程中并不清楚的一个地方,即我们在锁方案中该如何进行写或者读。
乐观:
FaRM使用的是一种乐观锁机制,在这种乐观锁机制中, 你可以在没有锁的情况下去读取数据。你只需要去读取数据就行了,你不用去知道你是否有权去读取该数据,或者有没有正在对该数据进行修改之类的操作。
在使用这种乐观锁方案的情况下,你不会直接写入数据,相反,你将数据缓存起来(buffer writes)直到事务最终结束。所以我们会将这些写操作缓存在client本地。当事务最终结束的时候,你会试着去提交该事务,这里会有一个验证阶段, 事务处理系统会试着弄清楚你所做的读和写操作是否与执行顺序一致——冲突,中止并重新执行;成功,提交。
(直到提交完成,一直不会堵塞)
在这里插入图片描述
(3)FaRM使用了乐观锁机制,因为它想去使用one-sided RDMA来对数据进行快速读取。所以,这个设计中确实是被RDMA强制使用的。
我们经常将乐观锁并发控制简写为OCC。

(4)这个设计为FaRM所做的事情是:我们的读操作可以去使用one-sided RDMA,因此,读取速度就会超级快,因为我们会在稍后去检查这些读操作是否成功读取到了数据。

5.FaRM使用主备份复制和乐观并发四阶段提交协议

四、FaRM的API

1.FaRm的研究原型并不支持SQL之类的东西,它使用了一个相当简单的API来支持事务,调用这个API,你能够知道transaction code是什么,那么它会在事务开头处标记声明下事务要开始执行了。

2.这一个完整的事务是由这些读操作和写操作所组成的(下图),我们通过调用txCreate()来声明一个新事务。
通过调用函数来显式读取对象,你得往里面传入一个对象标识符OID,以此来表示你想读取的对象是哪个。然后,你就会拿到某个对象。你可以在本地内存中对该对象进行修改,我们不会对它进行写入操作,通过调用txRead我们从服务器处拿到该对象的一个副本
当你想要更新一个对象的数据时,那么你就可以去调用txWrite。然后,你得往里面传入OID以及要更新的对象内容,之后你得告诉系统去提交这个事务,并让它对这个事务进行验证。如果验证通过,那么它就会这些写操作生效,并让其修改结果对外可见(当调用txCommit提交的时候,它做了很多事情,这个会在下文讨论Figure4的时候讲一下)。通过返回值ok,应用程序可以判断该事务提交是成功还是中止了。
在这里插入图片描述
3.上图是一个事务的例子,都是库中调用的API,比如txCreate,txWrite,txCommit,txRead。
txCommit作为一系列复杂的写调用,是很复杂的,它首先要去调用事务协调器的代码,这是两阶段提交的一个比较少见的变种,figure4中对此进行了描述。
当我们调用txRead时,它会去读取相关服务器上的数据。
这里的txWrite只会对本地的buffer数据进行修改,只有当这个修改过的数据对象发送到服务器后,它才会去提交该事务。

4.对象所对应的object id实际上是一个复合标识符,它们由两部分组成:区域编号和该区域中的内存地址。
区域编号是用来识别区域的,所有服务器上的内存会被拆分到不同的区域进行管理,配置管理器会去跟踪服务器所复制的区域编号是什么(上图中的region #)。根据给定的区域编号,client可以根据当前primary和backup中的表进行查找。
该区域中的内存地址:client通过地区编码来选择要去进行通信的primary和backup。接着它将地址告诉支持RDMA的NIC,并跟它说,请从这个地址去获取这个对象。

五、FaRM架构

在这里插入图片描述

  • 主备用户容错
  • 配置管理器(configuration manager):负责leases,detect failures, coordinate recovery

1.组成部分

(1)Cluster

论文中介绍的集群由90台机器组成, 承担了4.9T数据量。

(2)Machine

集群中的每一台Machine都可以接受客户端请求(必要时路由),都可以完成数据对象存储。

  • 对象: 包含64-bit 版本和数据内容,版本用于并发控制。
  • 区域: 多个对象聚合在一起存储,称为Region,每个Regions 2G大小。
  • 主从复制:采用常规的主从复制架构容错,每个区域包含1个Primary和 f个backup。对象写入和读取都只从Primary读取,backup只做热备。
  • Txlog : 事务log,用于事务恢复。
  • Msg Queue: 消息队列,用于RPC

(3)CM

ConfigManager. 集群中,同时只有一台。
主要功能有:管理 leases, regions在machine间的分布,检测失败, 故障恢复等管理工作。

(4)Zookeeper

Coordinator service。存储的内容是这样一个配置<i, S, F, CMi> :
where i is a unique, monotonically increasing 64-bit configuration identifier,
S is the set of machines in the configuration,
F is a mapping from machines to failure domains that are expected to fail independently (e.g., different racks),
CM ∈ S is the configuration manager。
配置是元组⟨i,S,F,CM⟩,其中i是唯一的,单调递增的64位配置标识符,S是配置中的一组计算机,F是从计算机到故障域的映射,CM则是配置管理器。FaRM使用Zookeeper来协调服务,确保机器就当前配置达成一致并进行存储。每个配置更改都会由CM调用一次Zookeeper,以更新配置。

2.服务器的内存布局

(1)服务器的内存中包含了一个或多个数据区域(Region),每个数据区域都可以看做是一个对象集合。
在每个对象的内部都有一个header,它里面包含了该对象的版本号(下图中的VERS),每个对象一次只会有一个版本号。在每个对象的header中的高位处会有一个lock标志位,低位处则是放着该数据的版本号。然后就是该对象的实际数据了(下图header的下方)。
每个对象在各个服务器中的内存布局都是相同的。

(2)每当系统对一个对象进行修改时,它会对该对象的版本号进行加1

(3)此外,这里面存放着多对消息队列(存储logs),在该系统中的其他服务器里,每个服务器都会有一份log日志,这意味着,如果该系统中有4台服务器在执行事务,在内存中就会有4份日志,它们可以通过RDMA来进行追加。
服务器会为每台服务器都创建一份日志,每个日志记录着对应服务器所执行的事务,我给这些队列编个号(下图LOGS 0 1 2 3)。比如:以服务器2所执行的事务代码为例,它要和这个服务器进行通信并追加日志,实际上它会将服务器2的日志追加到这个服务器的内存中。所有服务器的内存中总共会有N^2个队列。
实际上,这里有一组非易失性的日志。然后,这里可能还有一组单独的消息队列,它们用于处理RPC那样的通信(下图LOGS右侧)。
再次强调,在每个服务器上的incoming message queue,其他服务器可以通过RDMA来对它进行写入操作(A和B通过RNIC建立channel,B对在A中建立的incoming message queue进行直接写入)
在这里插入图片描述

3.FaRM内存具体操作

前面提到FaRM将所有内存放到一起进行管理,那具体怎么操作呢?

  • 内存以2GB进行划分,每个2GB称为一个region,然后每个region分布在一个primary,f个backup上
  • region到primary-backups关系保存在CM上
  • 应用可以指定目标机器,新分配的region将在这些机器上

4.如何申请内存

  • CM通过2PC阶段和副本进行通信,申请region
  • 在region可用前,mapping信息需要传递给所有副本

5.如何进行RDMA写

在这里插入图片描述
在读取上,每个机器都有一个ring buffer,实现了FIFO队列,在写的时候,sender通过RDMA直接写到尾部,将记录追加到日志中。然后NIC(网卡)直接给ACK,receiver周期性的从头部读取数据处理。它们用作事务日志或消息队列。NIC会确认这些写入,但是不会涉及接收方的CPU。接收者定期轮询日志的开头以处理记录。

6.FaRM地址空间
FaRM为应用程序提供了跨集群机器的全局地址空间的抽象,每个机器都运行独立的应用程序进程并存储对象在地址空间里。FaRM的API提供了对本地或者远程对象的透明访问,应用程序线程可以随时启动事务,在事务执行期间可以执行任意逻辑,随后可以调用FaRM来提交这些逻辑操作。
在这里插入图片描述
FaRM提供了如上图的一个抽象地址空间,在我们看来所有的数据都在一个全局的地址空间中,通过FaRM提供的API让我们能够透明的访问本地和远端的数据。

7.FaRM事务可见性

FaRM事务使用乐观并发控制,所有更新都被本地缓存,并且仅在成功提交后才对其他事务可见。如果并发事务冲突,事务的提交就会失败。

六、分布式事务和复制

1.primary-backup备份

2PC提交会有一个问题,coordinate如果挂了或者participants挂了,会影响整个进程,因此一个想法就是进行primary-backup备份,保证高可用,于是就有下面的图:
在这里插入图片描述
这种主备的模式每次消息都需要和Primary和Backup同时交互,并且需要消耗CPU。
而FaRM在提交上使用:

  • one-sided RDMA进行操作
  • 减少消息数量
  • 使用OCC(乐观并发控制)
    • version number(用户读数据的版本验证)
    • 整体流程:本地执行+锁写记录+验证读记录+提交并解锁

七、事务模型

1.FaRM集成了事务和备份的协议可以很好地提高性能,传统协议相比,它使用的消息更少,并且利用单面RDMA读取和写入来提高CPU效率和降低延迟。FaRM使用非易失性DRAM中的主备份复制来存储数据和事务日志,并使用单个事务协调器直接与primary和backup进行通信。

2.下图是FaRM事务的timeline。虚线和实线分别表示RDMA的读写,点线表示硬件的响应,矩形是对象数据。
在这里插入图片描述
在事务执行结束时,提交协议如下进行:

(1)Lock

  • 写lock record到写数据的primary上
  • primary尝试着锁住记录,然后进行回应

锁定阶段:协调器将LOCK记录写入每台机器上的日志,这是任何写入对象的主要记录。 该记录包含每个书面对象的版本和新值。 原语通过尝试使用CAS将对象锁定在指定的版本来处理这些消息。 他们发回一条消息,指示是否已成功获取所有锁。 如果从事务读取对象以来,任一对象版本被更改,或者另一个事务将对象锁定,则协调器将中止该事务并将中止记录写入所有主数据库的日志中。
只针对写的请求,只发送给primary。

(2)Validate

  • 通过RDMA进行读,然后比较version是否改变了
    验证阶段:协调器现在检查事务读取的所有对象的版本是否都未更改。 可以通过单面RDMA进行操作,而无需远程CPU参与。 如果主要对象持有的阈值tr对象超过某个阈值,则使用RPC代替(RPC变得便宜的的临界点是4)。 如果版本已更改,则事务中止。
    只针对读的请求,只发送给Primary。

(3)Commit backups

  • 通过RDMA写log到所有backups
  • coordinate等待所有backups回复

提交备份阶段:每次备份时,协调器都会将提交备份记录写入日志,并等待NIC的确认(不涉及远程CPU)。
只针对写的请求,只发送给backup

(4)commit primaries

  • 通过RDMA写commit-primary记录到每个primary
  • primary处理记录,然后unlock
  • 只要coordinator收到primary的NIC回复,就认为成功,返回给应用

提交主数据库阶段:每次备份时都从NIC收到确认(对于在出现故障时确保严格序列化是必要的),协调器会将提交主数据库记录写入每个主数据库的日志中。 一旦收到硬件确认,就可以向应用程序报告完成情况。 原语通过更新就位的对象,增加其版本并对其进行解锁来处理这些记录。
只针对写的请求,只发送给primary。

(5)Truncate

  • coordinator收到所有primary的回复后,进行truncate
  • 在提交其他日志的时候,捎带上truncate
  • backups在truncation的时候,进行数据的更新操作

截断:“备份和主记录将记录保留在日志中,直到被截断。 协调器在收到来自所有主节点的确认后,会在主节点上截断日志并延迟备份。 它通过在其他日志记录中附带截断的事务的标识符来实现。 备份会在截断时间将更新应用到其对象副本。”
内存优化。
总结:原子读写 + 事务乐观并发控制 + 两阶段提交。

3.在执行阶段,事务使用单向RDMA读取对象,并且它们在本地缓存写操作。协调器还记录所有访问对象的地址和版本,如果primary和backup与协调器位于同一个机器,对象访问会使用本地内存而不是RDMA来读取和写入日志。

4.总结提交事务:
(1)Lock:协调器将LOCK记录写入每台机器上的日志,这些机器是写入对象的主要机器。Primary的机器通过锁定特定版本对象的方式来处理这些记录。如果获取到所有的lock,那么将发送一条报告消息,否则会终止事务;
(2)Validate:协调器对primary机器执行读取验证,主要是读取所有对象的版本号,看是否一致。验证默认是通过单边的RDMA读取完成的;
(3)Commit backups:协调器在每次备份时将COMMITBACKBACK记录写入非易失性日志,然后等待NIC硬件的确认,而不是中断backup机器的CPU;
(4)Commit primaries:在COMMIT BACKUPS写入backup机器之后,协调器开始对每台机器提交COMMIT-PRIMARY。Primary会更新对象的版本号;
(5)Truncate:在协调器收到所有主节点的响应之后,就会通过在其他日志记录中附带截断事务的标识符来实现记录的截断;

八、FaRM中的OCC提交协议

本部分重点介绍Figure4中的内容,下图解释了FaRM所使用的OCC提交协议,下面会对这张图逐步分析,重点是并发控制这部分。这些步骤中除了进行为容错而生的replication以外,它还实现了事务的有序执行。
在这里插入图片描述

1.Execute phase

首先是执行阶段,这里的每个箭头所表示的意思是,这指的是在机器C(coordinator)上所执行的事务,当它需要进行读取操作时,它会去使用one-sided RDMA来读取相关primary服务器上内存中的数据。(上图虚线左侧Execute phase)
对于这里的3个不同数据分片来说,它们各有一个primary服务器和backup服务器。client需要去读取事务所需要的所有数据,包括它要去写入的所有数据,首先它得去读取数据,因为它需要去获取初始版本号。

2.Commit phase

当事务调用txcommit来表示它已经执行完所有的操作了(上图虚线右侧Commit phase)
client在调用txcommit时扮演了事务协调器的角色,它所使用的整个协议可以看做是种很精致的两阶段提交
(1)在第一阶段中,它们会来回发送消息,事务协调器发送了lock消息给primary,并等待它们进行回复。提交协议中第一个阶段是lock阶段,在这个阶段中,client会给每个primary发送它要访问的object id。
对于client要写入的每个对象来说,它需要将更新后的对象发送给相关的primary,并将其作为一个新的日志条目追加到primary的日志上。它会往它要写入的每个数据分片的primary上的日志中追加该对象的版本号以及该对象的新值
这里会发生的事情是一个对象是在primary1上,另一个是在primary2上。当这些操作完成的时候,这些新的日志记录就已经落地到了这些primary上的日志里面。
实际上,primary得去主动处理这些日志条目,因为它得做一大堆检查来验证该事务中这个primary所负责的部分能否进行提交。此时,我们得等待每个primary对它自己内存中的client日志进行轮询,检查是否有新的日志条目,如果有新的日志条目,就对其进行处理,接着,发送Yes或者No来告诉client它能否去执行该事务中的这部分操作。
当primary去轮询从client处所拿到的日志条目时,它会去做什么呢?
如果该objectid所对应的对象现在被锁上了,那么primary就会拒绝这个log消息,并使用RDMA发送一条消息给client,它会说No,我无法处理这个事务,所以我在两阶段提交中选择No。另一种情况则是该数据没有被锁,那么primary所做的另一件事就是去检查它的版本号,它会去确保clien发送给它的版本号和client一开始读取该数据时的版本号是一致的,这意味着,当我们的事务在执行读和写的期间,如果其他人对该数据对象进行了写入操作,该数据对象的版本号就会发生改变,那么primary就会给client回复一个No,并禁止该事务继续执行,但如果版本号没有改变,并且该数据对象没有上锁,那么,primary就会对该数据对象加锁,并返回一个成功的信号给client。
因为primary通过多CPU来执行多线程任务,这里面可能也有一些其他事务,存在有几个不同的事务要试着对同一个对象进行修改所导致的抢锁的情况。实际上,primary会使用一个原子指令(即compare-and-swap)来检查版本号以及锁。然后,它会执行一个原子操作,即对该数据版本进行上锁,这就是为什么要将lock标志位放在高位,而版本号放在低位的原因了。这样我们就可以通过一条指令来对版本号和lock标志位进行compare-and-set操作。
有一件事要注意一下,如果该对象
已经被锁住了
,那么这里就不会被阻塞住,也不用去等待该锁被释放,如果其他事务已经拿到这把锁了,那么primary直接返回一个No就可以了。
总结:Lock阶段,client发送要写入的对象到P1和P2,追加到日志中,P1和P2日常轮询日志发现新的client日志,查看要写入的对象是否有锁,如果有返回no。若没有,用原子指令cas看版本号是否一致,不一致返回no。若一致,上锁返回yes。

(2)回到client处的箭头这里(上图Serialization point),它扮演了事务协调器的角色,它会去等待该事务所修改对象的相关数据分片下的primary对它进行回复。只要它们中有一个回复的是No拒绝执行该事务,那么事务协调器就会中止整个事务,然后它会向所有参与该事务的primary发送消息,并说:我改变主意了,我不想去提交这个事务。但如果它们回复的都是Yes,那么事务协调器就会觉得实际上可以去提交这个事务,但primary并不知道其他primary投的是Yes还是No,事务协调器就得去通知所有primary告诉它们所有人投的都是Yes,并说,请你们去提交该事务。对于每个修改的对象来说,client会向该事务涉及的primary添加另一条记录来告诉它所有的人都投了Yes。
事务协调器会进入commit primary这一阶段(上图中4步骤),它会在commit primary阶段往每个primary的日志中追加日志条目,事务协调器只能去等待收到来自RDMA NIC的确认消息,它不用去等待primary去处理日志记录,只要事务协调器收到任何primary所发送的确认信息,它就可以返回Yes来表示这个事务已经执行成功。之后primary通过原语更新数据。
注:4步骤commit primary提交时只要收到primary确认信息就返回,不必等待primary处理完日志和更新完对象内容。

(3)在此之后,还有另一个阶段,一旦所有primary知道事务协调器已经提交了这个事务,那么你就可以告诉所有primary,它们可以丢掉与这个事务相关的日志条目了(上图中5步骤 TRUNCATE)。
现在最后要做的事情是,primary收到truncate之后,会去查看该事务日志,它们会对这些日志进行轮询,它们会注意到在某一时刻有一个commit primary记录,收到这个commit primary日志条目的primary会知道,它之前将这个对象锁住了,并且这个对象必须依然被锁住。所以primary要做的事情就是,使用先前收到的log消息中的新内容来更新副本内存中的这个对象,更新该对象相关的版本号,最后清除该对象上的锁,这意味着只要primary接收并处理了一条commit primary日志信息,它会对数据进行更新并将锁释放,它会将这个新数据暴露给其他事务,在这个时间点后的其他事务就可以去使用这个具有新值和新版本号的对象。
总结:上面(1)-(3)使用occ提交协议对数据写入的过程,属于两阶段提交。1步骤lock阶段,client传更新后的对象和版本号,primary判断是否已有锁和版本号,通过用cas加锁并返回yes。如果都是yes,就可以提交了。4步骤commit primary阶段,事务协调器(同时也是client)向所有更新数据的primary中增加commit primary记录,并等待primary的确认信息后返回(不必等待primary处理日志记录和更新对象内容)。最后是5步骤truncate,primary收到后知道该事务日志可以移除了,要去检查对应事务的日志,同时进行backup对应的内容修改和锁的释放。

3.例子

(1)假设我们有两个事务T1和T2,它们都想对x进行加1,x是某个服务器内存中的一个对象。
这些是关于FaRm所保证的执行顺序方面的东西,不管FaRm实际是怎么做的,它所执行的结果要和某一时间执行这两个事务所得的结果一致。
可能的结果:两个事务都提交、一个中止一个提交、都中止。
在这里插入图片描述
(2)参考下面的commit协议图,我们通过结合这几种不同的情况来看看它的提交协议。
它们在同一时间一起执行,它们在同一时刻发送它们所有的消息,它们在同一时刻进行读取,这里假设x的初始值为0。
在这里插入图片描述
如果在Execute phase它们发送完log消息返回的是Yes,那么它们就会在同一时间提交事务。它们做的都是读操作,因为one-sided read是不可能失败的。它们将完全一样的log消息发送给持有对象x的这个primary,它们所读取的版本号都是一样的。
它们都在同一时间发送log消息,它们会在它们的log消息中附带的值为1。两条传入的信息可以由这个primary上的不同CPU核心进行并行处理,但primary使用的关键指令是原子指令,即test-and-set或者compare-and-swap。primary会对其中一个事务先使用compare-and-swap指令进行处理,来设定该版本数据对象的lock标志位。当它对另一个事务执行原子命令compare-and-swap时,它会观察到该数据对象已经上锁了。其中一个事务会返回Yes,另一个事务在设置锁的时候会失败,它会观察到该数据对象已经上锁了,所以会返回No。
假设T2从primary处拿到的回复是NO。T1已经拿到锁并且收到了primary所返回的Yes,所以它会去提交事务,当primary收到了commit消息后(协议图4.commit primary向下箭头处),它会去更新这个对象,接着将lock标志位清空(相当于释放),并对版本号加一,接着返回True。
primary给另一个事务返回的是No,这也就是说txCommit返回的结果是false;这是我们允许的一个结果,但当然,这并不是我们所允许的唯一结果。
在这里插入图片描述
(3)还有其他可能的结果。读操作是并发执行的,假设T2先进行读操作,接着T1开始进行读操作,T1的执行速度比T2更快,它发送了它的log消息(1.lock阶段),并收到了来自primary的回复,然后进行提交(4.commit primary),在此之后,T2才开始做这个事情。
T1处的Lx消息会被成功处理,因为此时该数据对象相关的lock标志位还未被设置,因为T2此时还未发送lock消息去设置锁。T1处的C这个commit primary消息实际上会去清空该数据对象的lock标志位(即释放锁),所以这个lock标志位在此时就会被清空。
此时,T2会往primary的日志中追加lock条目,此时这个primary去查看这个数据对象时,它的lock标志位并没有被设置,primary会看到该数据对象的版本号,这个log消息包含了T2一开始读到的该数据对象的版本号。因为T1的commit primary已经增加了该数据对象的版本号,primary会看到该数据对象当前的版本号要比T2中的版本号要高,所以primary实际上会返回一个No给事务协调器(T2处的Lx),事务协调器会去中止这个事务,这里我们会拿到的x的值为1,其中一个事务会返回True,另一个事务则返回False,它的最终结果和之前的结果是一样的,这是我们所允许的结果。
在这里插入图片描述
(4)这里还有一种稍微不同的情况,其中一种可能是这个commit消息是在这个lock消息发送之后发送的,这和(2)中的情况是一样的,即T1拿到了锁,T2看到T1拿到了锁。
在这里插入图片描述
5)如下图的最后一种情况,T1发请求早会成功执行,因为在T1中并不存在抢锁的情况。当T2去读取x的时候,它会看到一个新版本号(由T1在commit primary时候修改过的),并且该lock标志位也没有被设置。接着当它给primary发送Iock日志条目时,primary中处理锁的代码会看到这里的lock标志位并没有被设置,允许它去提交事务。在这种情况下,两个事务返回都为true,我们看到的x值是2。
因为下图T2 Rx1处的读操作读取的不仅仅是新的版本号,它还读取了x的新值,即1。(两个事务串行执行了)
在这里插入图片描述
(6)在这些情况下x最终取值0、1、2的结果都是可能会出现的。

4.乐观锁并发控制为什么能提供有序执行呢?

如果事务间不存在冲突,那么版本号和lock标志位就不会改变。当我们第一次读取该对象时所拿到的版本号就会和上一个事务提交结束时的版本号相同。
如果在我们读取一个数据对象和尝试提交修改之间存在了一个冲突事务,这个冲突事务修改了某些东西。然后,在实际开始进行提交时,我们就会看到一个新版本号或者该lock标志位被设定上了。所以在我们第一次读取该数据对象与最终提交事务这段时间内,通过比较该数据对象的版本号以及lock标志位,它就会告诉你当你在使用这些东西的时候,是否有其他的提交也使用了这个对象。
很重要的一点是,使用这种乐观锁方案时并不需要去检查锁去进行读操作。当我们第一次使用该数据对象时,这允许我们使用速度超级快的one-sided RDMA来读取该数据对象

5.验证阶段

(1)上面介绍的这个系统是在没有验证和commit backup的情况下工作的。对于读取对象来说,验证这一步骤是对它的一种优化,但对于写入操作来说并不是。commit backup则是容错这方面的其中一部分方案。
(2)于我们在事务中读取对象(并不是写入对象)来说,验证这一阶段是对它的一种优化。如果是一个不修改任何数据的简单只读事务,这里优化所做的事情是,事务协调器可以通过onesided read来进行验证,速度超级快,这样我们就无须将东西放入日志并等待primary去查看我们的日志条目对其进行处理了。使用one-sided read的话速度就会变得非常非常快,本质上来讲,它代替了对象锁的作用它可以让只读变得更快。
验证阶段做的事情是,事务协调器会去刷新该对象的header。在执行阶段结束,当它要提交事务的时候,这里我们不会发送一个lock消息,并检查当前版本号是否和它一开始读取该对象时的版本号一致,它会去刷新该对象的header,它也会去检查这里的锁是否被释放,这就是它的工作方式。所以对于一个只读事务来说,比起发送lock消息,发送一个validate消息的速度要来得更快。
在这里插入图片描述
(3)关于验证阶段的事务案例
假设一开始: x = 0, y = 0;
先T1,再T2 : y = 1, x = 0;
先T2,再T1 : x = 1, y = 0;
无法得到的结果:x = 1, y = 1; ×
我们会通过这个测试来看看验证阶段会发生什么
在这里插入图片描述
a. 假设这两个事务会同时执行,我们要去读取x和y的值。因为T1对y进行了写入操作会去对y进行上锁,T2对于x也是一样。但因为这里我们使用的是对只读事务的验证优化,这意味着T2得去对y进行验证,T1需要对x进行验证(T1对x进行读取,但没有写入)。
所以T1会去对x进行验证,最后它们可能会进行提交操作。如果我们使用验证,正如上面所描述的,只是去检查版本号和lock标志位,版本号是否未被修改,lock标志位也没被设置,这样我们会得到一个正确的结果吗?
在这里插入图片描述
因为当这些lock消息被相关primary进行处理时,这就会导致lock标志位被设定上。假设最开始lock标志位并没有被设定上,当我们要进行验证的时候,虽然client对x和y的header进行的是one-sided read,但它会看到这些在处理lock请求时被设定上的lock标志符。所以它们会看到它们所读取的对象的lock标志位已经被设定上了,这两个事务都会中止,x或者y都不会被修改,这是其中一个合法的结果,这两个验证操作都会失败。
在这里插入图片描述
b.有的时候,其中一个事务可以执行成功。这里所发生的事情是,T1的执行速度会快一点,T2还没来得及对x加锁, 此时验证操作就会成功(下图T1的Vx)。在之前,y的lock标志位也被成功设定上了(下图T1中的Ly),因为这里也没有其他人对y进行修改。所以primary会对T1响应Yes, 通过one-sided read会看到一个未被修改的版本号以及lock标志位。
在这里插入图片描述
当primary处理lockx的时候(下图T2中的Lx),这一步操作会成功,因为没有人对x进行修改。当client执行T2时,会去
刷新y的锁标志位和版本号
(下图中T2中的Vy)(注:执行阶段是读到了最初的数据,验证阶段是刷新并对比数据,方便下一步进行),它看到的情况取决于T1是否提交,如果T1还未提交,这个验证操作就会看到y的lock标志位被设定了,如果事务T1已经提交了,那么这里的lock标志位就会被清空,但在这个验证操作中,通过one-sided read会看到一个与它一开始看到的版本号所不同的一个版本号。所以T1会被提交,T2会中止。
在这里插入图片描述

6.总结

如果这里有一个简单的只读事务,那么就不会经历一个locking阶段,我们可以通过one-sided RDMA来处理纯粹的只读事务,即在验证阶段使用one-sided RDMA进行读取,所以它们的速度会非常快。只读事务不需要服务器做太多工作,在FaRM中的所有东西都是非常精简的,部分原因是因为RDMA。
简单来讲,这里强制它在不检查锁的情况下能够进行读取。
事实证明,如果没有什么冲突发生,那么乐观锁并发控制的效果就会很棒,如果一直发生冲突的话,那么事务就得被中止。

九、故障恢复

1.五个阶段

FaRM中的故障恢复有以下五个阶段:故障检测,重新配置,事务状态恢复,批量数据恢复和分配器状态恢复。

2.故障检测

FaRM使用租约来检测故障。
除CM之外,每台计算机都在CM上拥有租约,而CM在其他每台计算机上都拥有租约。租约到期就会触发故障恢复,租约是通过3次握手来授予的:
每台机器向CM发送一个租赁请求,并以一条消息作为响应,该消息既充当对该计算机的租赁授权,又充当CM的租赁请求。然后非CM的计算机回复该租赁请求就行。

3.重新配置

Reconfiguration重新配置协议将FaRM实例从一种配置移到另一种。以下是重新配置的时间图:
在这里插入图片描述

  • 怀疑。当某个机器的租约在CM到期时,它将怀疑该机器发生了故障。然后屏蔽所有外部客户端请求。
  • 探测。新的CM向配置中的所有机器发出RDMA读取,但被怀疑的机器除外。同时也怀疑任何读取失败的机器,新CM仅在获得大多数探测的响应后才继续进行重新配置,避免CM处于小分区。
  • 更新配置。在收到对探针的答复后,新的CM尝试将存储在Zookeeper中的配置数据更新为⟨c+1,S,F和CMid⟩,其中c是当前配置标识符,S是已回复的探测器,F是计算机到故障域的映射,而CMid是其自身的标识符。
  • 重新映射区域。新CM重新分配先前映射到故障机器的区域,以将副本数恢复到f+1。
  • 发送新配置。重新映射区域后,CM将NEW-CONFIG消息发送到配置中的所有计算机,其中包含配置标识符,其自身的标识符,配置中其他计算机的标识符以及区域到计算机的所有新映射。
  • 应用新配置。当机器收到配置标识符大于其自身配置的NEW-CONFIG时,它将更新其当前配置标识符及其区域映射的缓存副本,并分配空间以容纳分配给它的所有新区域副本。
  • 提交新配置。一旦CM从配置中的所有计算机接收到NEW-CONFIG-ACK消息,它将等待以确保先前配置中授予该配置中的计算机的租约均已到期。然后,CM将NEW-CONFIG-COMMIT发送给所有配置成员,这些成员拿到了租约的授权;

4.Transaction state recovery

在配置更改之后,FaRM使用分布在因事务而修改对象副本所产生的日志来恢复事务状态。下图展示了事务恢复状态的timeline,FaRM通过在集群中的线程和机器之间分配工作来实现快速恢复。
在这里插入图片描述

(1)Block access to recovering regions.

当一个primary挂掉,backup会被配置选举成新的primary,此时所有对相关区域的访问都会被屏蔽,直到上图第四步完成,重新获取读写锁。

(2)Drain logs.

要确保跨配置的一致性,一般是拒绝来自旧配置的消息。但FaRM无法这样做,因为NIC会提交写入事务日志的COMMIT-BACKUP和COMMIT-PRIMARY记录,而不会考虑它们的发布配置。FaRM通过drain日志的方式解决这个问题,即在收到NEW-CONFIGCOMMIT消息时都会处理其日志中的所有记录。完成后,它们会将配置标识符记录在变量LastDrained中,配置标识符小于或等于LastDrained的事务日志记录将会被拒绝。

(3)Find recovering transactions.

所有机器必须就给定事务是否为恢复事务达成一致,FaRM通过在重新配置阶段在通信中附带一些额外的元数据来实现此目的。协调器读取每台计算机上的LastDrained变量,对于自LastDrained之后其映射被更改的每个区域r,CM都会在NEW-CONFIG消息中向该计算机发送两个配置标识符——LastPrimaryChange[r]和LastReplicaChange[r],分别是r的主备对象更改时的最后一个配置标识符,在配置c-1中开始提交的事务将在配置c中恢复。
用于恢复事务的记录可以分布在不同主数据库的日志中,以及由事务更新的备份机器中。region的每个备份都将NEED-RECOVERY消息与配置标识符,区域标识符以及更新该区域的恢复事务标识符一起发送给主数据库。

(4)Lock recovery.

每个region的primary都会一直等到本机的日志排干并且等待收到每台backup的NEED-RECOVERY消息,然后才去构建完整的恢复事务集合。然后,它通过其线程上的标识符对事务进行分片,以便每个线程t恢复具有协调器线程标识符t的事务状态。同时,主数据库中的线程并行地从尚未本地存储的备份中获取所有事务日志记录,然后锁定通过恢复事务修改的任何对象。
当某个区域的锁恢复完成时,该区域就处于活动状态,本地和远程协调器可以获得本地指针和RDMA引用。

(5)Replicate log records.

primary日志中的线程通过向backup发送缺失的事务的REPLICATE-TXSTATE消息来进行记录。该消息包含区域标识符,当前配置标识符以及与LOCK记录相同的数据。

(6)Vote.

正在恢复事务的协调器根据事务更新的每个区域的投票来决定是提交还是中止事务。

(7)Decide.

如果协调器收到来自任何region的commit-primary投票,则决定进行事务。否则,它将等待所有区域投票,如果至少一个区域对 commit-backup投票,而其他所有区域被事务投票锁定、提交备份或截断,则它将等待提交。

5.Recovering data

FaRM必须在某个region的新备份中恢复(重新复制)数据,以确保将来可以容忍复制失败。一个区域的新备份最初具有新分配的零区域副本。它将区域划分为多个工作线程,以并行方式恢复该工作线程。
在复制到备份之前,必须检查每个恢复的对象。如果对象的版本大于本地版本,则备份将通过比较和交换锁定本地版本,更新对象状态,然后将其解锁。

6. Recovering allocator state

FaRM分配器将区域划分为块(1 MB),用作分配小对象的slabs。它保留了两个元数据:块标头(包含对象的大小)和slab的空闲列表。
分配新块时,块头将复制到备份中。这样可确保它们在发生故障后在新的主数据库上可用。slab空闲列表仅保留在primary上,以减少对象分配的开销。

十、FaRM的限制

1.正如之前提到的,这里面还存在着一系列限制,比如在FaRM中,所有的数据都得放在内存中,所有的服务器都得放在同一个数据中心。然而,这仍被视为一种令人惊叹速度很快的分布式事务实现,这要比运用在生产中的任何系统都来得快。
2.,而且硬件也有点异类,它非常依赖于NVRAM方案,它也基于这些特殊的RDMA NIC,现在这些东西都还不是特别普遍,在性能方面,在数据中心里面RDMA非常流行,这样人们就可以使用这些技术了。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值