Master和Tablet Copy与配置更改集成
摘要
本文阐述了如何在Kudu的Raft实现的上下文中进行Tablet Copy。有关Kudu中Raft配置更改的详细信息,请参阅Raft配置更改设计文档。
目标
-
集成Tablet Copy以允许在“开始时间”的日志不再可用于重播时,将“快照”数据和日志复制到tablet副本。
-
Master需要容忍并促进动态一致性配置更改。
新的RPC API
新的 Master RPC APIs
Master将向管理员用户提供以下操作:
- AddReplica(tablet_id, TS to_add, Role role)
- RemoveReplica(tablet_id, TS to_remove)
新的tablet server Tablet Copy RPC
Tablet Copy允许将tablet快照移动到新服务器。每个tablet server上都有一个StartTabletCopy() RPC调用。当leader发现follower 需要的日志entry比leader使用的更老,或者检测到follower 没有给定的tablet时,它会向follower 发送RPC以指示follower 启动Tablet Copy。可以通过将follower 日志中的最新OpId作为参数传递,使该回调成为幂等的。
管理Tablet Copy作业
由于复制tablet可能涉及传输大量GB数据,因此我们可能需要支持对正在进行的Tablet Copy作业的操作可见性,在他们自己的线程池上运行它们,支持取消等。
Tablet Copy的设计与实现
Tablet 自动恢复
leader可以通过向tablet server发送StartTabletCopy RPC,使不存在的tablet自动创建/自动恢复。在将副本添加到一致性配置之前,不要求Master显式调用CreateTablet()RPC,这使得在Master端添加新成员的实现变得更简单。
此外,允许leader在其日志充分赶上leader的情况下将“pre-follower”(PRE_VOTER)提升为follower(VOTER)的设计,提供可用性优势,类似于Raft论文中指定的方法
通过这样的设计,将新副本添加到tablet的一致性配置中,主服务器只需要向leader发送RPC,告知它添加给定节点:
- Master -> Leader: AddServer(peer=Follower_new, role=PRE_VOTER)
然后,leader将负责检测tablet是否已过期或不存在,在哪种情况下必须复制,或者是否可以正常捕获。
tablet删除
如果随后从tablet server中删除副本,则必须对其进行逻辑删除并永久保留其(现在为静态)持久性Raft状态。不这样做可能会导致严重的闹裂问题。
为了安全地支持删除和Tablet Copy,我们给tablet定义了4种状态:DOES_NOT_EXIST, DELETED,COPYING和READY。DOES_NOT_EXIST意味着具有该名称的tablet不存在服务器上,DELETED意味着它已被逻辑删除,COPYING 意味着它正处于Tablet Copy过程中,而READY意味着它处于正常,一致的状态。有关平板电脑删除的更多详细信息,请参阅后面的部分
自动恢复Tablet Copy协议
leader 和follower 之间的Tablet Copy协议如下。leader 不断尝试发生心跳请求到follower :
Leader -> Follower: AppendEntries(from, to, term, prev_idx, ops[])
follower 接收请求,如果它运行正常响应心跳请求。如果它处于DOES_NOT_EXIST或者 DELETED状态会有特殊的反应。大概逻辑如下:
AppendEntries(from, to, term, prev_idx, ops[]):
if (to != self.uuid): return ERR_INVALID_NAME
if (term < self.current_term): return ERR_INVALID_TERM
if (self.state == DELETED): return DELETED
# Otherwise: Normal consensus update / AppendEntries logic
如果leader 得到一个DOES_NOT_EXIST或DELETED tablet状态,它将通过向follower 发送StartTabletCopy RPC,反复尝试在follower 上自动恢复tablet。
在follower上,来自leader 的StartTabletCopy RPC是幂等的。如果tablet不存在则具有创建tablet的逻辑如下:
StartTabletCopy(from, to, tablet, current_state, last_opid_in_log = NULL):
if (to != self.uuid): ERR_INVALID_NAME
if (this.tablet.state == COPYING): ERR_ALREADY_INPROGRESS
if (this.tablet.state != current_state): ERR_ILLEGAL_STATE
if (this.tablet.state == RUNNING):
DeleteTablet() # Quarantine the tablet data.
if (this.tablet.state == DELETED || this.tablet.state == DOES_NOT_EXIST):
CreateTablet(COPYING) # Create tablet in "COPYING" mode.
if (caller.term < self.term): ERR_BAD_TERM
if (last_opid_in_log != NULL && != this.log.last_op.id): ERR_ILLEGAL_STATE
RunTabletCopy() # Download the tablet data.
下面详细介绍了下载和更换数据的详细过程,详见“Follower Tablet Copy”。
tablet目录结构
本节介绍tablet server的目录结构,如下所示:
instance
tablet-meta/*<tablet-id>*
consensus-meta/*<tablet-id>*
wals/*<tablet-id>*/
data/
tablet存在的主要指标是tablet-meta目录中tablet的超级块文件的存在。如果该文件不存在,我们没有指向tablet数据块的指针,因此我们认为平板电脑处于 DOES_NOT_EXIST状态。除了超级块之外,启动tablet所需的文件是一致性元数据文件(在consensus-meta下),预写日志(在wals下)和数据块(在data下)。当然,自动恢复的tablet server级文件(如instance)也是必须的。
tablet删除
为了删除tablet,我们必须永久保留Raft元数据以避免一致性失忆症。我们还会临时备份(隔离)数据,以便以后进行调试。最初,我们将提供一些工具,以便在不再需要时手动删除隔离文件及其关联的数据块。
要求
- 保存一致性元数据,例如当前任期,投票历史记录和上次日志条目。这是为了避免选择过时的节点作为leader,并强迫过时的leader下台。
- 逻辑删除tablet(可能使用超级块中的标志),因此我们知道要查找上面保存的共识元数据。
- 隔离旧数据。
实现
我们可以使用以下步骤安全地实现tablet删除:
- 关机正在运行的tablet
- 创建隔离目录(QDIR)。
- 将当前的超级块复制到QDIR。
- 将SuperBlock标记为DELETED(因此我们总是在此步骤后回滚删除操作); 将日志中的最后一个OpId存储到SuperBlock
PB中的一个字段中(如果我们希望能够在删除后不再拥有我们的WAL文件时在选举中投票,我们需要知道我们日志中的最后一个OpId);将路径存储到QDIR中的SuperBlock;最后将SuperBlock保存并fsync到tablet元数据目录中。现在tablet被视为已删除。 - 将一致性元数据文件复制到QDIR。
- 将WAL目录移动到QDIR。这应该是原子的,因为此时我们不对WAL进行条带化。
Follower Tablet Copy
Tablet Copy从远程复制数据; 合并新旧元数据文件; 写一个替代SuperBlock。
- 在COPYING状态中重写当前的SuperBlock以及fsync。如果SuperBlock已经存在,请清除QDIR路径字段,但不清除last_opid字段(如果我们在此阶段崩溃并且必须在启动时删除自己,那么我们可以(必须)保留我们对last_opid信息以便能够投票)。
- 下载并合并共识元数据文件。
- 下载远程WAL。
- 下载远程块。
- 将替换SuperBlock写入READY状态并对其进行fsync。
- 启动新的tablet副本。
元数据合并
在下载远程元数据文件时,为了避免失忆,我们必须确保我们的术语保持单调,并且我们的投票记忆不会丢失。我们不能只采用远程共识元数据,因此我们必须将它与我们自己的元数据合并。这些规则确保在合并远程元数据时共识元数据保持有效:
- 合并元数据时始终采用最高任期。
- if remoteTerm <= localTerm:保留投票历史localTerm,如果有的话。
- 始终采用远程的raft集群成员资格配置。
tablet启动
这里有一个简单的状态机,用于在tablet启动时保持一致的状态:
- 如果tablet.state是DELETED&& WAL目录存在:重做上面“tablet删除”部分中的步骤#5和#6(回滚)。
- 如果tablet.state是COPYING:删除自己(返回DELETED状态)。
- 如果tablet.state是READY:正常启动。
- 如果tablet启动失败:保持离线。
添加新成员的master实现
由于master将新成员添加到共识配置时只需要调用一个幂等RPC,因此主服务器不需要存储有关其正在进行的操作或执行重试的元数据。策略(例如,备份策略)将做出这些决策并触发幂等操作。master不断评估其策略并进行调整。
当master确定需要更改tablet的配置时,它会执行以下操作:
- AddServer() RPC调用会为tablet指定先前提交的raft配置以保证请求是幂等的。
- master将定期发送此类RPC,直到它看到它已成功为止,例如在修复复制不足问题时。
幂等配置更改操作
为了让master在可能存在陈旧的一致性配置信息的同时做出决策,我们需要在“配置更改”操作(AddServer/ RemoveServer/ ChangeRole)中添加CompareAndSet样式的一致性参数。我们将添加committed_config_id作为可选参数,以标识最新提交的配置。如果opid与成员上当前提交的配置不匹配(在RaftConsensus ReplicaState锁下检查),则RPC返回错误,指示调用方已过期。如果OpId匹配,但已经有挂起的配置更改,则该请求也会被拒绝(该行为已经实现)。
其他故障恢复
具有一致性元数据的磁盘故障是灾难性故障
如果我们丢失了具有元数据或WAL的磁盘,并且需要复制新的副本以进行恢复,那么可能无法安全地执行此操作,这是因为我们不可避一致性失忆。在这种情况下,tablet server必须采用新的UUID并完全清除其所有数据和状态:
- 检测坏磁盘
- 关闭/崩溃
- 管理员将替换坏磁盘
- 擦除机器上的所有数据
- 为tablet server重新分配新的UUID
这一预防措施可确保过时的tablet server无法让失忆的服务器自动恢复新tablet,选出过时的服务器leader,并创建并行一致性组。
有可能对此进行优化,并避免在只有一个磁盘发生故障时必须对整个盒子进行核对。也许我们可以依赖RAID来获得共识元数据和WAL,或者我们可以尝试将tablet备份到特定磁盘上,或者做一些其他聪明的事情。然而,这些选项并不是很好,这个问题非常棘手。