【ETCD】[源码阅读]深度解析 Etcd领导怎么处理日志提案

本文将来深度解析ETCD的领导者在处理消息类型为MsgProp的具体逻辑,日志提议的处理过程中主要涉及到如下几个部分的逻辑:
1、验证提议是否有效:确保日志条目不为空、节点是集群的一部分,并且领导权转移没有进行中。
2、处理配置变更:如果提议包含配置变更,必须确保当前集群状态可以接受该变更。
3、追加日志条目:将日志条目追加到 Raft 日志中,若失败则丢弃提议。
4、广播日志条目:向集群中的其他节点广播日志条目,以实现日志复制。

代码解析:

1. 处理空日志条目
if len(m.Entries) == 0 {
    r.logger.Panicf("%x stepped empty MsgProp", r.id)
}
  • 目的:此检查确保接收到的日志条目不为空。如果 m.Entries 为空,则会触发 Panicf,并打印出日志。
  • 背景MsgProp 消息应包含至少一个日志条目(通常是用户提交的操作),如果为空,意味着客户端没有提交有效的日志,或有潜在错误。
2. 检查节点是否在 Raft 集群中
if r.prs.Progress[r.id] == nil {
    // If we are not currently a member of the range (i.e. this node
    // was removed from the configuration while serving as leader),
    // drop any new proposals.
    return ErrProposalDropped
}
  • 目的:检查领导者是否仍然是集群中的成员。Raft 协议中,节点可能会被从集群配置中删除。
  • 解析r.prs.Progress[r.id] 表示当前节点在 ProgressTracker 中的状态,如果该节点不再是集群成员(可能由于配置变更),则会拒绝任何新的提议。返回 ErrProposalDropped 表示提议被丢弃。
3. 检查领导转移
if r.leadTransferee != None {
    r.logger.Debugf("%x [term %d] transfer leadership to %x is in progress; dropping proposal", r.id, r.Term, r.leadTransferee)
    return ErrProposalDropped
}
  • 目的:检查领导者是否正在进行领导权转移。如果是,则拒绝新的日志提议。
  • 背景:Raft 协议允许在领导者之间转移控制,如果领导权转移正在进行中,新的日志提议会被丢弃以避免在转移期间的操作冲突。
4. 处理日志条目中的配置变更
for i := range m.Entries {
    e := &m.Entries[i]
    var cc pb.ConfChangeI
    if e.Type == pb.EntryConfChange {
        var ccc pb.ConfChange
        if err := ccc.Unmarshal(e.Data); err != nil {
            panic(err)
        }
        cc = ccc
    } else if e.Type == pb.EntryConfChangeV2 {
        var ccc pb.ConfChangeV2
        if err := ccc.Unmarshal(e.Data); err != nil {
            panic(err)
        }
        cc = ccc
    }
  • 目的:处理日志条目类型,如果条目是配置变更(EntryConfChangeEntryConfChangeV2),则解析该条目的配置变更数据。
  • 解析
    • e.Type == pb.EntryConfChangee.Type == pb.EntryConfChangeV2 分别对应两种类型的配置变更条目。
    • pb.ConfChangepb.ConfChangeV2 用于表示配置变更的具体数据。
    • Unmarshal 方法将字节数据转换为结构体类型,以便后续处理。
5. 检查配置变更的条件
if cc != nil {
    alreadyPending := r.pendingConfIndex > r.raftLog.applied
    alreadyJoint := len(r.prs.Config.Voters[1]) > 0
    wantsLeaveJoint := len(cc.AsV2().Changes) == 0

    var refused string
    if alreadyPending {
        refused = fmt.Sprintf("possible unapplied conf change at index %d (applied to %d)", r.pendingConfIndex, r.raftLog.applied)
    } else if alreadyJoint && !wantsLeaveJoint {
        refused = "must transition out of joint config first"
    } else if !alreadyJoint && wantsLeaveJoint {
        refused = "not in joint state; refusing empty conf change"
    }

    if refused != "" {
        r.logger.Infof("%x ignoring conf change %v at config %s: %s", r.id, cc, r.prs.Config, refused)
        m.Entries[i] = pb.Entry{Type: pb.EntryNormal}
    } else {
        r.pendingConfIndex = r.raftLog.lastIndex() + uint64(i) + 1
    }
}
  • 目的:对配置变更进行一些检查,确保当前集群状态可以接受该变更。
  • 具体解析
    • alreadyPending:检查是否存在尚未应用的配置变更。
    • alreadyJoint:检查当前是否处于联合配置状态(即配置变更过程中)。
    • wantsLeaveJoint:检查配置变更是否要求离开联合配置状态。
    • 如果上述条件不符合,日志条目会被忽略,类型会被重设为 EntryNormal
    • r.pendingConfIndex 更新为日志的最后索引,以标记配置变更的提交位置。
6. 提交日志条目
if !r.appendEntry(m.Entries...) {
    return ErrProposalDropped
}
  • 目的:将接收到的日志条目追加到 Raft 日志中。如果追加失败,则返回 ErrProposalDropped
  • 背景:这是 Raft 协议中的标准操作,领导者会将客户端请求作为日志条目追加到本地日志中。若日志追加失败,则表示提议无法处理。
7. 广播日志条目
r.bcastAppend()
  • 目的:广播追加的日志条目到集群中的其他节点,确保所有节点都能看到并处理这些日志。
  • 背景:在 Raft 协议中,领导者需要向所有跟随者发送日志条目,以保证一致性和日志复制的可靠性。
8. 返回
return nil
  • 目的:成功处理提议后返回 nil,表示没有错误。

总结:

这段代码实现了 ETCD 中 Raft 协议领导者节点处理客户端日志提议的核心逻辑。具体步骤包括:

  1. 验证提议是否有效:确保日志条目不为空、节点是集群的一部分,并且领导权转移没有进行中。
  2. 处理配置变更:如果提议包含配置变更,必须确保当前集群状态可以接受该变更。
  3. 追加日志条目:将日志条目追加到 Raft 日志中,若失败则丢弃提议。
  4. 广播日志条目:向集群中的其他节点广播日志条目,以实现日志复制。

这些操作确保了 Raft 协议的日志一致性和集群状态的稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值