如果 etcd raft 的日志复制乱序怎么办?

问题描述

我是 etcd 的新手,对日志复制有一些困惑:

  1. 例如,leader先发出{term:2,index:3},然后是{term:2,index:4},多数人也按顺序响应。但是由于网络延迟,leader收到的响应乱序,先收到{term:2,index:4}的响应。

sequence diagram

etcd 如何处理这种情况?好像只是忽略日志 {term:2,index:3},直接提交 {term:2,index:4}。

func (pr *Progress) MaybeUpdate(n uint64) bool {
    var updated bool
    if pr.Match < n {
        pr.Match = n
        updated = true
        pr.ProbeAcked()
    }
    pr.Next = max(pr.Next,n+1)
    return updated
}
  1. 当响应数据包(例如 {term:2,index:3} 的响应)丢失时,etcd 如何重试?我在 etcd 项目中找不到任何代码片段来处理这个问题。

解决方法

你问的问题比 etcd 更与 raft 相关(etcd 实现了 raft,所以它们仍然相关)。为了深入了解 raft 算法,我强烈建议您查看 raft webpageraft paper(写得真好!)。我相信第 5.3 节“日志复制”会有所帮助。

首先让我们打下基础:Leader 跟踪与每个 follower 匹配的条目。它将信息保存在论文中的 nextIndex[]matchIndex[] 中(检查图 2)以及 etcd 中的 ProgressMap

// ProgressMap is a map of *Progress.
type ProgressMap map[uint64]*Progress
type Progress struct {
    Match,Next uint64
    ...
}

现在让我们跳到您的问题。

  1. 例如,leader先发出{term:2,index:3},然后是{term:2,index:4},多数人也按顺序响应。但是由于网络延迟,leader收到的响应乱序,先收到{term:2,index:4}的响应。 etcd 如何处理这种情况?似乎只是忽略日志 {term:2,index:3},直接提交 {term:2,index:4}。

这一切都取决于追随者的状态(从领导者的角度来看)。让我们深入了解 StateProbeStateReplicate

StateProbe 中,leader 试图找出它应该发送给 follower 的条目。它当时发送一条消息并等待响应(这可能是拒绝响应,在这种情况下,领导者必须减少与此跟随者相关的 Next 并重试)。在这种状态下,不可能向同一个关注者发送 2 个不同的 MsgApp

StateReplicate 中,leader 假设网络稳定并发送(可能)许多 MsgApp 消息。让我们研究示例。

Match := 2Next := 2

关注者日志:[E1,E2](E代表“进入”)

领导者日志:[E1,E2]

在这种状态下,leader 收到了对条目 E3、E4 和 E5 的放置请求。假设最大批量大小为 2,因此所有新条目不能在单个消息中发送。 Leader 会发送 2 条消息:(Index: 3,Entries: [E3,E4])(Index: 5,Entries: [E5])。在获得第一个消息的 ack 之前,将发送第二个消息。在图片中的情况下,follower 获得第一条消息,检查它是否可以使用来自请求的 Index 附加它(检查在 (raft).handleAppendEntries > (raftLog).maybeAppend > (raftLog).matchTerm > (raftLog).term 中执行),将条目附加到它的日志并发送确认。稍后,follower 收到第二个请求并对其执行相同的操作(检查它是否可以附加并发送 ack)。

follower 在发送 ack 之前检查它是否可以附加条目的事实在这里很重要。一旦领导者收到任何消息的确认,就可以确保在跟随者的日志中填充所有条目直到 Index + len(Entries)(否则此消息将被拒绝而不是确认)。多亏了这一点,第一个 ack 是否延迟(甚至丢失)并不重要。

  1. 当响应数据包(例如 {term:2,index:3} 的响应)丢失时,etcd 如何重试?我在 etcd 项目中找不到任何代码片段来处理这个问题。

我现在将重点放在 etcd 上,因为在草稿纸中它被描述为“领导者无限期地重试 AppendEntries RPC”,这是相当没有建设性的。每隔很短的时间间隔,leader 就会向follower 发送MsgHeartbeat,follower 以MsgHeartbeatResp 响应。作为 MsgHeartbeatResp 处理的一部分,leader 会跟随

if pr.Match < r.raftLog.lastIndex() {
    r.sendAppend(m.From)
}

应读作:“如果关注者上没有任何条目,请向他发送第一个缺失的条目”。这可以看作是重试机制,因为没有跟随者的 ack,pr.Match 不会增加。