《不妥协:具有一致性、可用性和性能的分布式事务》部分翻译

第三节 编程模型和架构

  1. 客户端开启线程时会作为事务的coordinator
  2. 使用优化并发控制,修改是本地的,提交后才能被其他事务看到
  3. FaRM对成功提交的事务提供了严格的串行性
  4. 保证对单个对象操作的原子性,每次读总能返回最新的值
  5. 不提供跨对象操作的原子性,但如果违背串行性的话则会abort事务
  6. 在提交的时候才会进行检查
  7. 提供无锁的读(CAS),事务内操作对象越少越好
  8. 可以强制设置对象的locality hint
  9. 配置是⟨i, S, F , CM⟩,其中i是唯一单调增的64位配置id,S是配置的机器集合,F是Pair<机器, 独立故障域>,独立故障域一般是机架,CM是配置管理机器
  10. FaRM使用zk来确保机器同意当前配置,并通过Vertical Paxos保存配置.但不依赖zk去做租约、故障检测、coordinator恢复,zk一般时候是不工作的。而是利用RDMA实现快速恢复
  11. zk只有在配置被改变的时候才会被唤起
  12. FaRM的地址空间由2GB的region组成,所以一个512GB的机器大概有256个region,每个region保存在1个primary和f个backups,能容忍f个故障,每个region存储的非易失内存中,并且能够被其他机器通过RMDA直接读取(相当于挂载了一个内存到远程机器上)
  13. 一般会读primary。如果在本地有就读本地内存,如果远程有就读RDMA
  14. 每个对象有64位的版本号被用于并发控制和副本
  15. region id和primary/backup的映射关系(即RDMA引用)存在CM中,当region被复制的时候,CM的映射关系也要随着变更
  16. 机器联系CM去分配新的region,CM分配一个region id(单调增),并且CM选择相应的副本,副本的选择平衡了各个机器的region数,并受限于用户设定的本地性约束(locality constraint),然后它发送prepare message(带着region id)给被选择的副本,如果所有副本报告成功,CM会发送commit message给这些副本,这个两阶段协议确保region id和机器的映射是有效的并且在使用之前已经完成所有机器的复制
  17. 这种集中管理的方法相比于一致性hash更容易做负载均衡、在接近容量爆满的情况容易工作
  18. 如果一个机器有250个region,那么一个CM能处理几千个机器(250万以上个region)
  19. 每一个机器使用ring buffer来实现FIFO队列,用来存储事务日志和作为消息队列,每一个Pair<发送者,接收者>都有单独的日志、消息队列,在物理上存储于接收者,发送者使用 单端RMDA写append到队尾;接收者周期性地从队头拿到日志来处理,当接收者truncate日志的时候,才会lazily地告诉发送者,这样能让发送者重新利用接收者的ring buffer中被释放的内存

第四节 分布式事务和副本

  1. FaRM融合了事务协议和副本协议来提高性能,比传统协议(比如Spanner的分布式事务+Paxos)有更少的传输消息,利用单端RDMA读写来提高cpu的有效性和低延迟
  2. FaRM在非易失的内存中使用主备副本协议来存储数据和事务日志,coordinator没有副本,并且coordinator直接和副本的primary、backup直接通信(而不是连接primary,primary转发给backup)
  3. 像其他事务系统一样,使用优化并发控制来做读校验
  4. commit阶段包括:lock、validation、commit backup、commit primary、truncate
  5. lock阶段:coordinator写lock日志到所有被修改对象的primary中,包含版本、新值和region列表。primary使用CAS尝试锁住这些对象的指定版本,返回是否锁成功的消息。如果某些从事务开始后任何对象的版本变更,或者有些对象被其他事务锁住,lock都会失败,这种情况下事务会被coordinator abort,coordinator写abort记录到所有primary并返回错误给应用
  6. 校验阶段:coordinator对事务内所有的只读对象进行读校验,从这些只读对象的primary发起RMDA读或RPC读(默认使用RDMA读,如果一个primary含有本次事务中只读对象的数量达到4个,则使用RPC,这种情况下RDMA读和RPC读基本相等,数量越多RPC读的性能就会高于RDMA读,这意味着我们需要在事务内尽量少的操作只读对象来进行调优)从而得到对象的最新版本号,如果版本号变更了,事务就被abort
  7. 提交backup阶段:coordinator写COMMIT-BACKUP记录到每一个backup中,并且等待网卡硬件的响应而不是cpu,写的内容和lock内容一样
  8. 提交primary阶段:等所有的提交backup写有响应后,coordinator写一个COMMIT-PRIMARY到每一个被写对象的primary节点中。只要有一个网卡响应成功或者本地写结束,coordinator马上返回给应用成功。primary处理COMMIT-PRIMARY的方式是,原地修改需要修改的对象的新值、增加版本号、对这些对象解锁,解锁也意味着这个事务中的所有写操作已提交
  9. truncate阶段:在coordinator收到所有COMMIT-PRIMARY的响应后,会lazily地truncate事务内的primary和backup的日志(给primary和backup追加truncate日志)

正确性:

  1. 写事务在lock最后一个写对象时具有串行性,只读事务在最后一个读操作时具有串行性,这是因为在这两个具有串行性的时刻能够保证读写对象的版本和本次事务执行阶段中看到的版本一样。锁阶段保证了写对象的串行性,而校验阶段保证了只读对象的串行性,在没有失败的情况下,等价于在串行性点原子地执行和提交了整个事务
  2. 串行性在FaRM是严格的:串行点开始于执行阶段的开始,结束于报告给应用的时候
  3. 为了保证机器故障的时候还有串行性,需要等所有的COMMIT-BACKUP硬件返回后才开始写COMMIT-PRIMARY,否则当某些COMMIT-BACKUP失败,且coordinator挂了,将会丢失记录:假设失败的提交到b机器,包含一个region r,如果coordinator先向应用返回成功,然后coordinator挂了,那么region r的非b副本将没法恢复数据
  4. 由于读的集合只保存在coordinator中的,所以如果coordinator挂了之后将没有commit记录去证明校验阶段成功,这种情况下会导致事务被abort。所以coordinator需要等一个primary提交成功后才能响应给应用成功,这样即使coordinator挂了之后,我们也能从commit的primary推断校验通过。这样就能保证至少有一个副本能存活在f个故障中,否则有可能还没COMMIT-PRIMARY之前,f个backup和coordinator一起挂了就会丢失数据,因为只有LOCK记录存活下来,并且可能没有校验成功的记录
  5. 传统的二阶段提交协议,可以在prepare阶段去检查有没有资源。但是在FaRM中不行,因为引入的单端的RDMA,这种方式没有引入远程的CPU,所以coordinator必须要预留空间。coordinator为所有提交协议记录预留空间,包括在开始commit之前截断primary和backup的记录。日志预留对于coordinator来说是本地操作,因为coordinator写记录到它自己拥有的participant中(这个我理解是Pair<发送者,接收者>的日志buf在接收者上,并且不同Pair有不同空间,因此不可能有多个机器同时操作一个Pair,可以认为没有冲突,所以是本地操作)。当相应的记录被写的时候,预留空间被释放。
  6. 当truncation记录被其他消息装载的时候,truncate记录预留被释放?
  7. 如果日志快满了,coordinator会使用预留空间去写truncate记录去释放日志空间,这个操作不太常见,但是能保证活性

第五节 故障恢复

  1. FaRM使用副本来提供持久化和高可用,这里的故障假定可以从非易失内存去恢复丢失的内容。FaRM依赖有界时钟漂移去做到安全性,依赖最终有界消息延迟去做到活性。
  2. 即使整个集群发生故障或停电,只要每个对象丢失副本数不超过f,就能从非易失的内存中恢复。FaRM能在网络分区中维持可用,只要其中一个分区中包含超过半数的机器,且每一个机器可以互联,且能够和大部分的zk服务节点互联,且包含每个对象至少一个副本。
  3. FaRM的故障恢复有5个阶段:故障检测、重新配置、事务状态恢复、批量数据恢复、分配器状态恢复。

故障检测

  1. FaRM使用租约来检测故障。除了CM,其他每个机器上都依靠租约保存了一个CM,反过来,CM上也依靠租约保存其他所有机器(双向租约)。任何租约的过期将会导致故障恢复。
  2. 租约使用三次握手的方式授权(这里应该是RDMA send):每个机器(除了CM)发送租约请求到CM上,然后CM返回消息代表CM授权了机器的租约,并且也代表了CM像该机器请求租约,最后该机器授权租约给CM。
  3. FaRM的租约及其的短,这是实现高可用的关键。在90个机器的高负载的场景下,FaRM使用5毫秒的租约能够不出现故障的误判。在更大的集群中可能需要两层结构,在最坏的情况下会让故障检测时间加倍。
  4. 为了在高负载的情况下获得短租约,需要小心地设计。FaRM使用独立队列pair来做租约,这样就能避免租约消息的延迟。否则各种消息混杂在一个队列里面,可能会被其他类型的消息推迟。
  5. 使用可靠地传输需要在CM中增加对其他每一个机器的额外队列的pair,这将会导致低性能,因为会被网卡的队列pair缓存容量丢失。所以FaRM的租约管理器使用无限带宽去发送和接收租约通过无连接的不可靠数据包(UDP?),这对于网卡来说,只需要增加一个额外的队列pair。默认情况下,租约的延续一般是租约超时周期的五分之一来应对潜在的消息丢失。
  6. 租约的延期必须得CPU定时调度,FaRM使用了独立的租约管理器线程,并且设置为最高的用户空间的优先级(在Windows系统上是31),并且租约管理器线程没有固定到任何的硬件的线程,它使用中断而不是轮询来避免开启一个重要的,一定要跑在每一个硬件线程上的OS task。这种方式增加了几毫秒的消息延迟,但对于租约来说不是问题。FaRM也测试过在硬件线程上跑租约管理器,一般情况下不会影响其他线程,但有时会被其他更高优先级的线程抢占,从而导致它在其他硬件线程上运行,因此在使用短租约时,把租约管理器固定到到硬件线程可能会导致误报租约过期。(我感觉意思就是,租约管理器线程可以被任意的硬件线程来跑,否则如果绑定一个硬件线程的话,可能会被高优先级线程抢占导致推迟执行)
  7. 最后,FaRM在租约管理器初始化的时候,已经预分配号所有需要的内存,并且把马上切换到代码页,且固定代码不变,来避免内存管理的延迟。

重新配置

  1. 重新配置协议把一个FaRM的实例从一个配置修改成留一个配置,使用单端RDMA操作很重要,因为能够获取很好的性能,但是这个方式对重新配置协议有新的需求。比如去实现一致性的一般技术是使用租约:服务器收到访问对象的请求,在返回消息之前检查对应请求机器的租约是否存在。如果一个服务器从配置中被剔除了,系统会保证租约到期之前,该服务器持有的对象不能被修改。FaRM在外部客户端使用消息发出请求与系统发生通信时,使用这种技术。但是由于在FaRM的配置中的机器使用了RDMA读而不引入远程机器的CPU,所以远程机器的CPU无法检查是否它自己持有租约。当前的网卡硬件无法支持租约,但不清楚未来是否支持。
  2. 我们通过精准的成员列表去解决这个问题,在发生故障后,新配置中的机器必须要在允许对象被修改之前同意新的成员列表。这就允许了在客户端做检查而不是服务端。在配置中的机器不会往不在配置中的机器发送请求,并且从不在配置中的机器发回的响应将会被忽略。

重新配置的7个步骤

  1. 推测。当CM上的一个机器的租约过期时,CM会推测那个机器挂了,并初始化重新配置,这个时间点开始阻塞所有外部客户端的请求。如果一个非CM机器上的CM租约过期了,也推断CM挂了,这个非CM的机器会尝试请求少量的CM备机去初始化配置(比如CM有k个继任者使用了一致性hash)。如果配置在一段时间周期后未改变,它会尝试自己去重新配置。这种设计避免了在CM故障时会有大量的同时的尝试重新配置。在所有情况中,初始化了重新配置的机器将会尝试变成新的CM作为重新配置中的一部分。
  2. 探测。新的CM发出一个RDMA读给所有的配置中的、除了被推测故障的机器。任何读失败的机器也会被怀疑发生故障。这个读探测通过一个配置可以允许处理会影响多台机器的相关故障,比如电源和开关故障。新的CM只会在大多数探测返回时进行重配置处理。这个机制允许在网络分区的情况下,不会在小分区中生成新的CM。
  3. 更新配置。CM尝试更新zk的配置为⟨c + 1, S, F , CM(id)⟩,C是当前的配置版本号id,S是探测有返回的机器列表,F是故障域映射(即机架和机器的映射),CM(id)是自己的id。FaRM使用zk的znode序列号去实现原子的CAS,只有当前配置的的版本仍然是c是,CAS才成功。这个机制保证了只有一个机器能够从原来的配置变成新的配置,并且成为新的CM(前面的探测步骤可能有多个机器连到大部分机器,所以还是可能会有多个机器尝试去更新配置)。
  4. 重新映射region。新的CM把故障机器上的region重新分配到其他机器上,让所有副本数都达到f+1,这里会有负载均衡,同时还会满足locality hint和独立故障域的约束,如果挂的机器含有primary副本,则让其中一个其他存活的副本晋升成为primary,从而减少恢复时间。如果检测到region丢失了所有的副本或者没有空间去复制region的时候,会触发一个错误信号。
  5. 发送新的配置。CM发送NEW-CONFIG消息给所有在新配置中的机器,如果CM改变了,NEW-CONFIG会重置租约协议,否则租约的交换还是继续运行,这样能够在重新配置阶段更快地检测到新增的故障。
  6. 应用新的配置。当一个机器收到NEW-CONFIG,并且配置id比自己的要大时,将会更新配置id并cache region的映射,并且分配新空间去赋予新的region副本。从这一时刻开始,这个机器将不会发送请求给不在新配置中的机器,并且会拒绝他们的读写请求。同时也会阻塞外部客户端的请求。然后,收到NEW-CONFIG的机器会返回NEW-CONFIG-ACK消息给CM。如果新配置的CM和旧配置的CM不同,NEW-CONFIG-ACK还代表着给CM租约授权,也代表着该机器向CM发送租约申请。
  7. 提交新的配置。一旦CM收到新配置中所有机器的NEW-CONFIG-ACK消息,CM会等待所有不在新配置中的机器的租约过期。然后CM发送NEW-CONFIG-COMMIT给所有新配置中的机器作为新配置的提交,以及第6步租约申请的授权(这里可以看到FaRM通过融合了分布式事务、副本协议来节省消息数)。最后所有成员解锁外部客户端请求,并且初始化下一步骤:事务状态的恢复

事务状态恢复

在配置改变后,FaRM使用被事务修改的对象所在的副本的日志来来恢复事务。这不仅需要恢复事务中被修改对象的状态,还要恢复coordinator在这个事务中的决定结果。这一步能够快速的恢复是因为跨节点的并发、并行执行。恢复步骤有如下7个:

  1. 阻塞访问正在恢复的region。当一个primary的region挂了的时候,其中一个备份会在重新配置阶段晋升为primary。在所有更新该region的操作反映在新primary之前,我们不能允许访问该region。我们通过阻塞region的本地指针的请求和RDMA引用请求来实现这一点,阻塞直到第4步当所有该region的写操作已经获得锁的时候
  2. 清除日志。单端RDMA写一般会和故障恢复冲突,一个比较通用的保持一致性的方法是拒绝来自旧配置的消息。FaRM无法实现这种方式,因为不管是从哪个配置中的机器发出的,网卡总是承认COMMIT-BACKUP和COMMIT-PRIMARY记录写到日志记录中。由于coordinator只等待这些响应,然后才告诉应用修改成功,所以当处理这些日志的时候,机器无法总是能拒绝从先前的配置来的记录。我们通过清除日志来确保所有相关的记录在恢复时被处理来解决这一难点:所有机器在收到NEW-CONFIG-COMMIT消息时处理所有的自己的日志记录。当完成的时候,它们使用一个LastDrained变量来记录配置id。FaRM在开始提交事务的时候,分配了一个唯一的id:⟨c, m, t, l⟩ ,这个id对事务commit开始后的配置c进行编码,m代表coordinator机器id,t代表coordinator的线程id,l代表thread-local的唯一id。配置id小于或等于LastDrained的事务日志记录会被拒绝。
  3. 找到正在恢复的日志。一个恢复事务是其提交阶段跨越配置更改,并且由于重新配置而更改了写入对象的某些副本、读取对象的某些主对象或协调器的事务。在日志排出期间,将检查每个日志中每个日志记录中的事务id和更新的region id列表,以确定恢复事务的集合。只有恢复事务才会在primary和副本中进行事务恢复,而协调器只拒绝用于恢复事务的硬件ack。所有机器必须要承认一个事务到底是不是恢复事务。我们通过在重新配置阶段通信时装载一些额外的元数据来做到这一点。CM读取每个机器的LastDrained变量作为探测读。每一个从LastDrained开始之后映射关系被改变的region r,CM会发送两个配置id在NEW-CONFIG消息中到r的机器。这就是LastPrimaryChange[r],代表当r的primary改变后,最新的配置id。还有LastReplicaChange[r],代表当任意的r的任意副本改变后,最新的配置id。一个已经开始提交的、配置id是c-1的事务会被恢复除非:region r的所有被修改对象都是被LastReplicaChange[r] < c的事务修改的,对于所有region r′,所有被读的对象都是被LastPrimaryChange[r′] < c的事务读取的,并且coordinator在配置c中没有被删除。被恢复的事务记录可能分布在被该事务修改的不同的primary和backup中,region的每一个backup发送一个NEED-RECOVERY消息给primary,该消息会带上配置id、region id和被恢复的、修改了该region的事务的id列表。
  4. 锁定恢复。为了去构建被region影响的恢复事务的完成集合,region的每个primary会等待直到本地机器日志已经被排出,并且从所有backup中收到NEED-RECOVERY消息。然后通过事务的id划分到不同的线程,以便每个线程t来恢复coordinator的线程t的事务状态。primary的线程并行地从backup中拉取任意的、本地没有存储的事务日志记录,然后对任何被恢复事务修改的对象进行锁定。当锁定恢复完成了一个region时,这个region就可以被本地或远程的coordinator获得本地指针和RDMA引用,可以允许它们在随后步骤并行的在这个region上读对象和提交写
  5. 备份日志记录。在primary中的线程为了它们丢失的事务通过发送REPLICATE-TX-STATE消息给backup来备份日志记录。消息包含了region id、当前的配置id和相同的的数据作为锁记录
  6. 投票。该恢复事务的coordinator基于每个被该事务修改的region的投票决定是否提交或abort事务。投票是从primary发给每一个region的。FaRM使用一致性hash去决定一个事务的coordinator,确保所有的primary独立地同意一个恢复事务的的coordinator的身份。如果coordinator的机器还在配置中,那么coordinator不会改变,但如果coordinator失败了,那么新的coordinator将分散在集群中。对于每一个修改了该region的恢复事务,primary的线程发送RECOVERY-VOTE消息给coordinator中对应的线程。投票就是只要任意的副本看到COMMIT-PRIMARY或COMMIT-RECOVERY则commit-primary;如果任意副本看到COMMIT-BACKUP并且死了,没有看到ABORT-RECOVERY,则投票commit-backup;如果任意副本看到LOCK记录并且没有ABORT-RECOVERY则投票lock;其他情况下投票abort。投票消息包含配置id、region id、事务id和被事务修改的region id列表。一些primary可能不会投票,因为它可能没有收到该事务的日志记录,或者是它们已经truncate日志记录。coordinator发送明确的投票请求给那些一个超时周期(设置为250微秒)还未投票的primary。REQUEST-VOTE消息包含了配置id、region id、事务id。有日志记录的primary在为了事务完成的第一次等待日志复制之后,像之前说的那样进行投票。如果日志被trancate,没有日志记录的primary投票truncated。如果没有被trancate,没有日志记录的primary投票unknown。为了确定一个事务的日志是否被truncate,每个线程维护了被truncate的事务id集合。这个集合通过使用未被truncate的事务id的下届来保持压缩。这个下界基于每个coordinator的下界来进行更新,在重新配置阶段期间,下界会被装载在coordinator的消息中。
  7. 决定。如果从任何的region收到了commit-primary,coordinator就会决定提交事务;如果至少有一个region投票了commit-backup并且所有其他的被事务修改的region提交了lock或commit-backup或truncated,则等待所有region去投票和提交;其他情况会abort。coordinator发送COMMIT-RECOVERY或ABORT-RECOVERY给所有的参与者副本。这两个消息都包含了配置id和事务id。COMMIT-RECOVERY的处理方式和一个primary收到COMMIT-PRIMARY和backup收到COMMIT-BACKUP的处理方式类似。ABORT-RECOVERY的处理方式和ABORT的处理方式类似。在coordinator收到所有primary和backup的响应后会发送TRUNCATE-RECOVERY消息。
正确性

接下来我们提供一些在直觉上感觉为什么这样和常规做法很不一样的事务恢复步骤是怎么确保严格的串行性的。关键的想法就是保护先前是已提交或abort的事务的结果。当primary暴露了事务被修改,或者coordinator告诉给应用已提交的时候,事务是已提交的。当coordinator发送abort信息或者告诉应用事务已经abort的时候,事务是abort的。对于事务还未决定结果的,恢复可能导致提交或abort,但是它保证从其他故障中进行的任何恢复都能保护结果。

未恢复的事务结果使用未发生故障时的普通情况协议来确定,我们不需要讨论这种情况。

一个已提交的事务在正在恢复的时候,系统保证在排出日志之前或期间会被处理和accepted的。这是正确的,因为primary只有在处理了COMMIT-PRIMARY记录后才暴露修改。如果coordinator已经告诉给应用,那么就一定说明返回了所有COMMIT-BACKUP记录硬件的响应,并且在接收NEW-CONFIG(因为它在配置变更后忽略了响应)之前至少收到有一个COMMIT-PRIMARY记录的硬件响应。所以当新的配置包含了每个region至少一个副本、至少一个region的至少一个副本将会处理COMMIT-PRIMARY或COMMIT-BACKUP记录,并且每个region的至少一个副本将会处理COMMIT-PRIMARY、COMMIT-BACKUP或LOCK记录。

第3步和第4步保证了由事务修改的region的primary能看到这些记录(除非它们被truncate了)。为了保证及时在随后又发生故障时,投票能产生相同的结果,primary把这些记录复制这些记录到backup。然后primary基于它看到的这些记录去投票。

这个决定的步骤保证了coordinator能够对先前已提交的事务在恢复时决定提交。如果任何的副本truncate了事务记录,那么所有的primary会投票commit-primary或commit-backup或truncated。至少一个primary会发送除truncate之外的投票,否则事务无法恢复。如果没有副本truncate了记录,那么至少有一个primary会投票commit-primary或commit-backup,而其他的primary会投票commit-primary或commit-backup或lock。类似的,如果一个事务之前是abort的,那么coordinator在恢复阶段也会决定abort,因为在这种情况下,要么没有commit-primary或commit-backup记录,要么所有的副本都会收到ABORT-RECOVERY。

阻塞访问恢复region(第1步)和锁恢复(第4步)保证了直到一个恢复事务已经被提交或abort为止,都没有其他操作能访问被修改的对象。

性能

FaRM使用多个优化从而得到快速的故障恢复。识别恢复事务将恢复工作限制在受重新配置影响的事务和region这一小范围内,当大型集群中的一台机器出现故障时,这些事务和region可能是总数的一小部分。我们的结果说明了这能够从一大堆事务中减少一个数量级,从而得到少部分需要恢复的事务。恢复工作是跨region、机器、线程的并行执行的。在锁恢复阶段后立即让region变得可用可以提高前台工作得心梗,因为访问这些region的新事务不需要锁很久。效果比较显著的一点是,新事务不需要等这些region的新副本被更新,而这些新副本的更新需要在网络大批量地移动数据的。

恢复数据

  1. FaRM一定会恢复region数据(重新复制)数据到新的backup上,以便将来能容忍f个故障。恢复正常操作不需要数据恢复,所以我们推迟它直到所有region变成active去最小化对重延迟的锁恢复的影响。当所有机器的primary变成active时,每个机器发送一个REGIONS-ACTIVE消息给CM。收到所有的REGIONS-ACTIVE消息后,CM发送一个ALL-REGIONS-ACTIVE消息给配置中的所有机器。从这个时间点开始,FaRM开始在前台并行地为新的backup恢复数据。
  2. 一个region的一个新的backup初始化空间为0。region被划分给worker线程并行地恢复数据。每一个线程发出一个单端RDMA操作去读primary的一个block。我们当先使用的是8k的block,对于网络的效率来说足够大,但对于用户的正常操作来说影响足够小。为了减少前台性能的影响,恢复被放在上一次读取开始后的随机时间间隔后(被设置为4毫秒)
  3. 每一个被恢复的对象一定要在被复制到backup之前检查。如果如果该对象的版本大于本地的版本,则backup使用CAS更新对象状态,否则被恢复的数据则不会被应用。

恢复分配器状态

  1. FaRM的分配器划分region成block(1MB)备用为slabs来分配小对象。它持有两种元数据:block的header:包含了对象大小、slab的空闲列表。当一个新的block被分配时,block header被复制到backup。这样能确保在故障后他们在primary是可用的。由于block header被用于数据恢复,新的primary在收到NEW-CONFIG-COMMIT之后立即发送这些header到所有的backup。这样可以避免在旧主节点故障时,复制header期间出现任何不一致。
  2. slab空闲列表仅保存在primary中,减少对象分配的开销。每个对象有一个bit在header中,会被分配器设置,在执行事务期间会被free清除。对象状态的更改在事务提交期间复制,如第4节所述。在故障后空闲列表会在新的primary通过扫描region中的对象来恢复,这个操作在该机器的所有线程上并行操作。为了最小化事务锁恢复的影响,分配器的恢复在收到ALL-REGIONS-ACTIVE消息后之后。为了最小化对前台工作的影响,每100微妙扫描100个对象。对象的回收会排队知道slab空闲列表被恢复。

添加新评论