etcd-raft代码学习笔记(上)

915 阅读8分钟

一、简介

  • 主要用途:服务发现、共享配置 组件
  • 相对于zookeeper优势:
    • 一致性协议更易于理解
    • 调用方式 http+json/grpc,跨平台
    • 支持tls,支持鉴权
    • 很多开源项目已经应用,最典型的k8s
  • 数据模型
    • key-value存储模型
    • 提供watcher机制,客户端可检测特定key值变化
    • 支持多版本, 全局自增main version + 事务编号subversion
    • b+树 存储key,加速查找,支持范围查找
    • etcd v3使用boltDB作为底层存储
核心代码结构
  • raftexample: etcd作者为读者写的业务侧介于raft library的实现demo,能快速帮助理解etcd使用
  • etcdctl:模拟客户端命令行工具,可以通过该工具向已经启动的集群写入/读取配置
  • raft:核心算法库
  • raft-http 网络层的实现
  • wal/snap:write ahead log && 快照存储
  • store:v2版的内存存储实现
  • mvcc:v3版的实现,使用boltDB
  • lease:租约,相同租约的keys使用同一个lease,提高性能
  • auth&&alarm:权限&&报警
  • etcdserver:对raft库的封装实现,提供节点的完整功能的实现
  • client:http+json客户端实现
  • clientv3;grpc客户端实现

二、 raft协议

raft是基于管理/复制日志的一致性算法,相比于更早出现的paxos协议,更容易理解,工程上更容易实现,paxos在工程上的实现大多为不同的改进版本,如zookeeper的zap协议就是对paxos的改良版

raft节点角色
    • leader:处理客户端请求,发送心跳给follower
    • follower:转发请求给leader,和leader保持心跳
    • candidate:长时间未受到leader心跳,follower=>candidate,发起竞选
    • raft 协议各角色转换关系如下图

竞选大致过程


当follower长时间未收到leader心跳消息时,会切换为candidate,并向其他follower发起投票请求,请求其他follower投票给自己,其他follower判断candidate是否满足当选为leader的条件,满足则投票给candidate,当candidate收到半数以上投票时,晋升为leader。

leader竞选过程如下图所示

  

竞选失败情况

当票数没有超过半数的时候,竞选失败,再发起新的一轮竞选

首轮竞选票数不够导致失败的场景如下图



客户端写入数据到集群的大致过程

客户端 => leader => leader将数据写入本地日志 => 同步日志给follower => leader 收到多数返回 后将本地log设置为commited状态 => 返回响应给客户端 => 然后再同步给follower刚才同步的日志切换到commited状态

客户端数据提交流程如下图





日志复制机制中的重要概念
  • term:leader任期号,自增+1,leader切换时会发生自增
  • index:日志编号,自增+1。raft日志是顺序写入的,每次写入index都会+1
term和index是raft中非常重要的属性,竞选过程中,如果candidate的term和index小于其他节点时,会竞选失败
  • committed index:写入多数node的log会被设置为commited状态,日志写入时,之后leader的log变为commited状态时,才会返回客户端的写入响应
  • Applied index:数据写入之后,随着leader和follower之间的通信,log会被设置为applied状态,通常情况applied index 和committed index 很近,applied index <= commited index
  • Match index:leader节点存储的已经与follower同步完的log index
  • Next index:下一次leader要发送给follower的log index位置
leader宕机后的日志复制流程的恢复过程
集群初始状态,A为leader,且知道C的match index和nextIndex


当节点B竞选为leader之后,会把A和C的matchindex初始为0,nextindex初始为自己的最新值,然后向A和C发送log,发送过程中发现C的日志和自己相差很多,会前移c的nextindex。leaderA宕机,B升为新leader的日志复制过程如下图

三、 raft.go代码实现

基本概念

  • term:leader 任期号,逐渐递增
  • index:日志索引号,自增
  • entry: raft日志,包含上述两个属性
  • message:raft节点通信消息,包含entry
  • raft struct:存储当前raft节点信息的核心结构体,里面涵盖了raft算法的核心属性和raft节点间交互的方法以及raft 存储 storage
  • config struct:创建raft struct时需要的配置参数
  • storage : raft节点的实际存储,config中的属性,通过config赋值给raft struct中的raftlog。
  • raft提供了storage的接口定义,并给出了内存存储实现memory storage
  • memory storage包括:hard state(节点状态)、snapshot(压缩的entry快照)、entry日志,entry是无限递增的,raft提供了压缩机制,把旧的日志压缩成snapshot进行存储,防止存储无限膨胀到机器扛不下
  • memory storage struct 提供了增删改查index 和 snapshot的方法

Memorystorage 结构如下图





unstable
  • 顾名思义,非持久化的entry log,raft node收到消息后先放入非持久化的内存中,然后放入storage中。
  • 好处:提交响应性能,业务方实现storage时,可能用的不是etcd提供的MemoryStorage,而是自己实现了storage接口,存储到硬盘,这时候unstable的优势就显现出来
  • 缺点:宕机时可能丢log
  • Unstable struct 和memory storage 属性和方法基本类似

unstable和memorystorage关系如下
Message消息类型
Raft node之间通信时,会把entry打包成Message进行交互
  • MsgHub:超时未收到eader心跳且electionElapsed > tickElectionTimeout时 ,触发竞选消息发送,tickElection方法里会触发此类消息发送
  • MsgPreVote:参与竞选的节点会向其他节点发送预投票消息
  • MsgPreVoteResp:预投票结果消息,当收到超过半数的结果时,当前节点的状态会从precandidate => candidate
  • MsgVote:当节点角色切换为Candidate时发送投票请求消息
  • MsgVoteResp:投票结果消息,当节点收到超过半数投票时,成为leader
  • MsgHeartBeat:leader向follower发送的心跳消息,触发条件 heartbeatElapsed >= heartbeatTimeout
  • MsgHeartbeatResp:Follower收到心跳消息后,进行回复
  • MsgApp:leader根据心跳响应数据,通过MsgApp消息向follower发送日志
  • MsgAppResp:follower收到leader日志数据后的响应,leader根据响应设置progress对象里下一次要发送给follower的index
  • MsgSnap:当leader给follower发送日志发现progress记载的next index远远落后于自己的entry日志时,发送snapshot给follower,这里有一点需要注意,follower收到snapshot后返回给leader的是MsgAppResp,告诉leader自己的index
  • MsgCheckQuorum:leader探活消息,当electionElapsed > tickElectionTimeout时,leader会检测自己时候还和集群中的大多数follower保持连接,如果没有,自动切换为follower。是否和多数follower相连更新progress对象统计判读,
  • MsgProp:客户端往etcd集群发消息时会被封装成MsgProp消息给leader处理,leader更新自己的entry log后,封装MsgApp消息同步给其他follower
  • MsgReadIndex:客户端请求读leader数据时,可能已经出现了脑裂,网络分区的问题,进而有可能读到旧的leader的数据。ReadIndex主要解决该问题,在客户端请求读数据时,先检查leader是否还为当前分区的leader,读的数据是否小于leader的日志的commit index,是则返回数据
  • MsgReadIndexResp:follower节点对上述消息的响应
  • MsgUnreachable&&MsgSnapStatus:本地消息,当leader向follower复制日志的过程中,发生异常会生成上述两个本地消息,更改progress的状态,进而控制下次发送follower日志的行为
  • MsgTransferLeader:运维主动迁主的时候,raft会包装成MsgTransferLeader消息给leader处理,leader收到消息停止客户端处理MsgProp请求,并向follower发送MsgTimeoutNow消息,让他们参与竞选
  • MsgTimeoutNow:follower收到消息,参与竞选
Raft node 之间的通信机制
  • tick定时器:raft node在启动的时候,会run一个for select循环,循环中会触发tick定时器去检查node当前状态,如是否正常收到leader心跳,是否该向follower发送心跳,是否要切换竞选状态等。tick定时器会根据当前状态把下一步要做的事情打包成Message,本地消息分别由step func做相应处理
  • Step func会先对message对第一步处理,local msg 处理完直接return,要发送给其他节点的msg送入msg队列
  • 消息队列:run起来 for select 循环中还有一个检测msg queue是否有待发消息的逻辑,有则交raft node中的transport对象处理,通过发送给其他节点
  • transport:transport负责raft node节点间的通信,负责把自己节点的message发送出去,把接收的消息交给step func处理

四、代码注释


五、未来

  1. 阅读etcd wal模块和transport模块的代码
  2. 阅读公司etcd自己定制化部分的实现