您的位置:

etcdraft 实现原理分析

一、etcdraft 图解

etcd 是一个高可用、强一致性、分布式键值存储服务,etcdraft 是其实现的核心算法。当前的 etcd 版本已经更新到 V3,而 etcdraft 不仅仅被用在 etcd 中,也成为了分布式一致性的主流实现之一。

etcdraft 实现原理是以 Raft 算法为基础,具有较高的可读性和实用性,下面我们通过图解来详细讲解一下 etcdraft 算法实现原理:

上图用来表示了一个 5 个节点(以下角色代表)的集群架构,其中每个节点都可以是 Leader,Follower,Candidate 三种角色中的一种。Leader 负责接收客户端的写请求,并将该请求复制到集群内其他节点,当超过半数节点写入成功后,才认为该写请求被提交。

节点间通过发送消息来进行通讯,其中包含 Leader 发送心跳、Append 请求等消息,Follower 和 Candidate 发送 Vote 请求等消息。

二、etcdraft 算法实现原理分析

1. 节点角色转变

在 etcdraft 中,每个节点的角色是不断变化的,当满足某些条件后,节点的角色会由 Follower 转换成 Candidate 或者 Leader。主要条件包括:

  • 没有收到 Leader 心跳包的 Follower 发现自己与 Leader 失去联系,它尝试发起新一轮的选举以成为新的 Leader。同时,其他 Follower 会收到该 Candidate 发送的 Vote 请求,如果同意成为其 Follower,就会将自己的投票票据上报给 Candidate。
  • Candidate 收到了超过半数节点的投票票据,就会成为新的 Leader。
  • Leader 在一段时间内没有向任何 Follower 发送心跳消息,Follower 认为 Leader 发现了某些问题,于是开始自己的选举。

2. 日志复制

当客户端提交写请求时,Leader 首先会将该请求写入自己的 Raft Log 并同步到集群内所有节点,当超过半数节点成功写入后,Leader 通知所有节点已经成功写入。同时,在该过程中,Leader 会对每一条写请求记录其 Term 和 Index,并将该记录称为一个 Entry,放入 Raft Log 中。

而 Follower 节点则定期与 Leader 进行数据同步,通过 Leader 的 Append 请求来进行 Raft Log 的复制,并定期向 Leader 发送心跳包。

3. 日志压缩

日志压缩是 etcdraft 中的一项重要优化,通过定期对 Raft Log 中已经提交的 Entry 进行压缩,可以提高读写性能。当 Leader 成功提交一个 Entry 后,Follower 也会对该 Entry 进行标记,等待 Leader 发送下一条 Append 请求,如果在下一个请求发送前 Entry 已被提交,则 Follower 会将已经提交的 Entry 之前的 Entry 条目进行清理。此外,Follower 还可以把 Raft Log 中已经被提交的数据删除,从而可以达到压缩 Raft Log 的目的。

三、etcdraft 源码阅读

etcdraft 源码结构


etcdraft/
├── id.go            // 节点 ID 生成器
├── raft.go          // raft 算法实现和接口定义
├── raft_test.go     // etcdraft 算法测试
└── raftpb/
     ├── raft.pb.go  // protobuf 文件
     └── raft.proto  // protobuf 定义

代码中的具体注释可以参考源码。其中,id.go 文件用来生成节点 ID,是 etcdraft 集群的重要参数之一。raft.pb.go 是通过 protobuf 定义 etcdraft 中的 message,这些 message 是 etcdraft 节点之间的通信方式。

etcdraft 算法核心

etcdraft 算法中定义了一些核心的接口函数,它们包括:

  • StartNode:创建一个 Node 对象,并使用该 Node 对象开始 Raft 集群实例。
  • Stop:终止 Raft 集群实例。
  • Tick:在 Raft 集群实例的主循环中被定期调用,用于实现 Raft 集群的时间控制,例如选举等。
  • Process:处理 Raft 集群实例节点之间的 RPC 消息,并返回响应。
  • Propose:向 Raft Log 中追加一条新记录。
  • ReadIndex:使用 ETCD 集群内的余数 Voting 算法,来对一个新读取进行投票。

etcdraft 源码分析

以 ProcessRPC 为例:


// ProcessRPC processes RPC messages sent by remote peers.
func (n *node) Process(ctx context.Context, m pb.Message) error {
    // 等待管道缓冲区可用
    wg.Wait()

    switch m.Type {
    case pb.MsgProp:
        // Propose 算法
        return n.handlePropose(ctx, m)

    case pb.MsgPropResp:
        // 更新 Leader 算法
        return n.handleProposeResp(m)

    case pb.MsgApp:
        // 将消息添加到所有日志中
        return n.handleAppendEntries(ctx, m)

    case pb.MsgAppResp:
        // 处理 APPEND 投票请求
        return n.handleAppendEntriesResp(m)

    case pb.MsgReqVote:
        // 处理节点选举
        return n.handleVoteReq(m)

    case pb.MsgReqVoteResp:
        // 更新当前行进中的选举
        return n.handleVoteResp(m)

    case pb.MsgSnap:
        // 处理快照请求
        return n.handleSnapshot(m)

    case pb.MsgHeartbeat:
        // 处理心跳包
        return n.handleHeartbeat(m)

    case pb.MsgHeartbeatResp:
        // 处理心跳响应包
        return n.handleHeartbeatResp(m)

    case pb.MsgTransferLeader:
        // 处理节点的转移管理
        return n.handleTransferLeader(m)

    case pb.MsgTimeoutNow:
        // 处理节点超时数据
        return n.handleTimeoutNow(m)

    case pb.MsgReadIndex:
        // 处理 ETCD 集群搜索功能
        return n.handleReadIndex(ctx, m)

    case pb.MsgReadIndexResp:
        // 管理 ETCD 集群搜索的响应
        return n.handleReadIndexResp(m)

    case pb.MsgUnreachable:
        // 处理消息传递失败的消息
        return n.handleUnreachable(m)

    default:
        // 处理未知消息
        n.logger.Debugf("unknown message type %v", m.Type)
        return nil
    }
}

在 etcdraft 中,Process 是 etcdraft 节点之间通信协议的核心函数,其中包含了大量消息类型的处理逻辑,需要特别注意的是:

  • MsgReadIndex:涉及到 ETCD 集群内的分布式搜索功能,而 MsgProp:涉及到的是类型为 Propose 消息的提交,这是 etcdraft 中关键的角色。
  • MsgPropResp:是处理提交消息并返回,同时还需要处理非 Leader 的情况。
  • MsgReqVote:称为 RequestVote 的投票协议,管理了提交的提名,并检查该节点是否有资格成为 Leader。

以上就是 etcdraft 实现原理的详细介绍,本文着重从 etcdraft 图解、etcdraft 算法实现原理分析、etcdraft 源码阅读、etcdraft raft 源码分析四个方面,梳理了 etcdraft 中的核心算法和关键代码,读者可以结合实际应用场景去深入了解 etcdraft。