结合consul raft库理解raft
一 入口 github.com/hashicorp/consul/agent/consul/server.go
func (s *Server) setupRaft() error {
状态机,用于consul 信息的查询,如kv等
s.fsm, err = fsm.New(s.tombstoneGC, s.config.LogOutput)
...
用于raft的rpc
trans := raft.NewNetworkTransportWithConfig(transConfig)
...
store, err := raftboltdb.NewBoltStore(filepath.Join(path, "raft.db"))
...
持久化任期和选举的信息
stable = store
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
...
存储日志
log = cacheStore
...
snapshots, err := raft.NewFileSnapshotStore(path, snapshotsRetained, s.config.LogOutput)
...
快照
snap = snapshots
...
创建raft
s.raft, err = raft.NewRaft(s.config.RaftConfig, s.fsm, log, stable, snap, trans)
...
}
二 对应的函数简介
1 状态机
github.com/hashicorp/consul/agent/consul/fsm/fsm.go中
func New(gc *state.TombstoneGC, logOutput io.Writer) (*FSM, error) {
初始化一个内存数据库
stateNew, err := state.NewStateStore(gc)
...
}
func (c *FSM) Apply(log *raft.Log) interface{} {
...
查找日志对应的处理函数
if fn := c.apply[msgType]; fn != nil {
return fn(buf[1:], log.Index)
}
...
}
github.com/hashicorp/consul/agent/consul/fsm/commands_oss.go中
注册日志处理函数
func init() {
...
registerCommand(structs.KVSRequestType, (*FSM).applyKVSOperation)
...
}
func (c *FSM) applyKVSOperation(buf []byte, index uint64) interface{} {
...
switch req.Op {
case api.KVSet:
return c.state.KVSSet(index, &req.DirEnt)
...
}
...
}
github.com/hashicorp/consul/agent/consul/state/state_store.go中
内存数据库
func NewStateStore(gc *TombstoneGC) (*Store, error) {
...
...
}
github.com/hashicorp/consul/agent/consul/state/kvs.go
func (s *Store) KVSSet(idx uint64, entry *structs.DirEntry) error {
tx := s.db.Txn(true)
defer tx.Abort()
if err := s.kvsSetTxn(tx, idx, entry, false); err != nil {
return err
}
tx.Commit()
return nil
}
2 raft rpc
处理rpc请求
github.com/hashicorp/raft/net_transport.go中
func NewNetworkTransportWithConfig(
config *NetworkTransportConfig,
) *NetworkTransport {
...
go trans.listen()
...
}
监听
func (n *NetworkTransport) listen() {
for{
...
go n.handleConn(conn)
}
处理和响应入口
func (n *NetworkTransport) handleConn(conn net.Conn) {
for {
if err := n.handleCommand(r, dec, enc); err != nil {
...
}
if err := w.Flush(); err != nil {
...
}
}
处理入库
func (n *NetworkTransport) handleCommand(r *bufio.Reader, dec *codec.Decoder, enc *codec.Encoder) error {
...
select {
发送给请求消费方
case n.consumeCh <- rpc:
case <-n.shutdownCh:
return ErrTransportShutdown
}
...
select {
case resp := <-respCh:
case <-n.shutdownCh:
return ErrTransportShutdown
}
...
}
消费方获取consumech,这个对应的是raft里的rpcch
func (n *NetworkTransport) Consumer() <-chan RPC {
return n.consumeCh
}
3 raft
github.com/hashicorp/raft/api.go中
func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, snaps SnapshotStore, trans Transport) (*Raft, error)
{
...
启动raft流程
r.goFunc(r.run)
启动状态机
r.goFunc(r.runFSM)
启动定期生成快照
r.goFunc(r.runSnapshots)
...
}
github.com/hashicorp/raft/raft.go中
raft身份的
func (r *Raft) run() {
...
switch r.getState() {
case Follower:
r.runFollower()
case Candidate:
r.runCandidate()
case Leader:
r.runLeader()
}
...
}
跟随者
func (r *Raft) runFollower() {
for {
选举复制等rpc请求
case rpc := <-r.rpcCh:
r.processRPC(rpc)
}
...
非leader拒绝apply
case a := <-r.applyCh:
a.respond(ErrNotLeader)
非leader拒绝认证leader
case v := <-r.verifyCh:
v.respond(ErrNotLeader)
...
来自leader的心跳超时
case <-heartbeatTimer:
lastContact := r.LastContact()
if time.Now().Sub(lastContact) < r.conf.HeartbeatTimeout {
continue
}
...
成为候选人
r.setState(Candidate)
...
}
候选人
func (r *Raft) runCandidate() {
选自己
voteCh := r.electSelf()
随机选举超时
electionTimer := randomTimeout(r.conf.ElectionTimeout)
多数派
votesNeeded := r.quorumSize()
,,,
for r.getState() == Candidate {
这时候有复制复制的话,会回退到follower
case rpc := <-r.rpcCh:
r.processRPC(rpc)
case vote := <-voteCh:
对方任期更大,自己回退到follower
if vote.Term > r.getCurrentTerm() {
r.setState(Follower)
r.setCurrentTerm(vote.Term)
return
}
收到选票
if vote.Granted {
grantedVotes++
}
达成多数派
if grantedVotes >= votesNeeded {
r.setState(Leader)
r.setLeader(r.localAddr)
return
}
}
}
func (r *Raft) electSelf() <-chan *voteResult {
任期+1
r.setCurrentTerm(r.getCurrentTerm() + 1)
获取持久化的最新日志index和任期
lastIdx, lastTerm := r.getLastEntry()
req := &RequestVoteRequest{
RPCHeader: r.getRPCHeader(),
Term: r.getCurrentTerm(),
Candidate: r.trans.EncodePeer(r.localID, r.localAddr),
LastLogIndex: lastIdx,
LastLogTerm: lastTerm,
}
askPeer := func(peer Server) {
resp := &voteResult{voterID: peer.ID}
err := r.trans.RequestVote(peer.ID, peer.Address, req, &resp.RequestVoteResponse)
if err != nil {
r.logger.Printf("[ERR] raft: Failed to make RequestVote RPC to %v: %v", peer, err)
resp.Term = req.Term
resp.Granted = false
}
respCh <- resp
}
for _, server := range r.configurations.latest.Servers {
判断peer是否有选举权
if server.Suffrage == Voter {
...
}
}
挑选日志复制和选举rpc分析
func (r *Raft) processRPC(rpc RPC) {
...
switch cmd := rpc.Command.(type) {
case *AppendEntriesRequest:
r.appendEntries(rpc, cmd)
case *RequestVoteRequest:
r.requestVote(rpc, cmd)
...
}
}
日志复制
func (r *Raft) appendEntries(rpc RPC, a *AppendEntriesRequest) {
忽略更小的任期
if a.Term < r.getCurrentTerm() {
return
}
收到更大任期的复制,自己会退到follower,更新任期
if a.Term > r.getCurrentTerm() || r.getState() != Follower {
r.setState(Follower)
r.setCurrentTerm(a.Term)
resp.Term = a.Term
}
if a.PrevLogEntry > 0 {
lastIdx, lastTerm := r.getLastEntry()
var prevLogTerm uint64
if a.PrevLogEntry == lastIdx {
prevLogTerm = lastTerm
} else {
var prevLog Log
if err := r.logs.GetLog(a.PrevLogEntry, &prevLog); err != nil {
复制中会用来知道logindex不匹配,需要回退匹配
resp.NoRetryBackoff = true
return
}
prevLogTerm = prevLog.Term
}
if a.PrevLogTerm != prevLogTerm {
resp.NoRetryBackoff = true
return
}
...
resp.Success = true
r.setLastContact()
return
}
启动领导
func (r *Raft) runLeader() {
启动复制
r.startStopReplication()
当选后发送一个noop日志,启动复制流程,否则会日志安全问题
noop := &logFuture{
log: Log{
Type: LogNoop,
},
}
r.dispatchLogs([]*logFuture{noop})
r.leaderLoop()
}
领导执行循环
func (r *Raft) leaderLoop() {
...
for r.getState() == Leader {
case rpc := <-r.rpcCh:
r.processRPC(rpc)
follower复制时响应的任期大于自己的任期,需要回退到follower
case <-r.leaderState.stepDown:
r.setState(Follower)
apply达成多数派
case <-r.leaderState.commitCh:
...
响应上层的命令
case newLog := <-r.applyCh:
ready := []*logFuture{newLog}
for i := 0; i < r.conf.MaxAppendEntries; i++ {
select {
case newLog := <-r.applyCh:
ready = append(ready, newLog)
default:
break
}
}
...
r.dispatchLogs(ready)
租约到期
case <-lease:
检查租约有效性
maxDiff := r.checkLeaderLease()
checkInterval := r.conf.LeaderLeaseTimeout - maxDiff
if checkInterval < minCheckInterval {
checkInterval = minCheckInterval
}
lease = time.After(checkInterval)
}
}
检查租约有效性,如果距离和大部分的follower的交互时间点的时间段中最大并未超过租约时长,那么可以继续leader,否则回退follower
func (r *Raft) checkLeaderLease() time.Duration {
var maxDiff time.Duration
now := time.Now()
for peer, f := range r.leaderState.replState {
diff := now.Sub(f.LastContact())
if diff <= r.conf.LeaderLeaseTimeout {
contacted++
if diff > maxDiff {
maxDiff = diff
}
} else {
...
}
quorum := r.quorumSize()
if contacted < quorum {
r.setState(Follower)
}
}
分发日志复写
func (r *Raft) dispatchLogs(applyLogs []*logFuture) {
for _, f := range r.leaderState.replState {
asyncNotifyCh(f.triggerCh)
}
}
启动复制
func (r *Raft) startStopReplication() {
for _, server := range r.configurations.latest.Servers {
...
r.goFunc(func() { r.replicate(s) })
...
}
}
复制入口
func (r *Raft) replicate(s *followerReplication) {
...
r.goFunc(func() { r.heartbeat(s, stopHeartbeat) })
...
for !shouldStop {
...
case <-s.triggerCh:
lastLogIdx, _ := r.getLastLog()
shouldStop = r.replicateTo(s, lastLogIdx)
...
}
}
复制操作
func (r *Raft) replicateTo(s *followerReplication, lastIndex uint64) (shouldStop bool) {
...
日志定期会进行快照,然后删除,如果跟随者的log index本地未找到,那就发送日志快照
if err := r.setupAppendEntries(s, &req, s.nextIndex, lastIndex); err == ErrLogNotFound {
goto SEND_SNAP
}else if err!=nil {
return
}
...
if err := r.trans.AppendEntries(s.peer.ID, s.peer.Address, &req, &resp); err != nil {
...
}
...
if resp.Success {
updateLastAppended(s, &req)
...
} else {
回退nextindex 进行尝试复制
s.nextIndex = max(min(s.nextIndex-1, resp.LastLog+1), 1)
if resp.NoRetryBackoff {
s.failures = 0
} else {
s.failures ++
}
}
if s.nextIndex <= lastIndex {
goto START
}
发送日志快照
SEND_SNAP:
if stop, err := r.sendLatestSnapshot(s); stop {
return true
} else if err != nil {
r.logger.Printf("[ERR] raft: Failed to send snapshot to %v: %v", s.peer, err)
return
}
}
成功复制给一个follower,更新状态
func updateLastAppended(s *followerReplication, req *AppendEntriesRequest) {
if logs := req.Entries; len(logs) > 0 {
last := logs[len(logs)-1]
s.nextIndex = last.Index + 1
s.commitment.match(s.peer.ID, last.Index)
}
}
更新follower的复制index
func (c *commitment) match(server ServerID, matchIndex uint64) {
if prev, hasVote := c.matchIndexes[server]; hasVote && matchIndex > prev {
c.matchIndexes[server] = matchIndex
c.recalculate()
}
}
计算是否就一条日志复制达成多数派
func (c *commitment) recalculate() {
...
复制达成多数派,通知leader commitch
if quorumMatchIndex > c.commitIndex && quorumMatchIndex >= c.startIndex {
c.commitIndex = quorumMatchIndex
asyncNotifyCh(c.commitCh)
}
...
}
原文地址:https://www.jianshu.com/p/66c1f68e8d63
结合consul raft库理解raft的更多相关文章
- 通过Consul Raft库打造自己的分布式系统
通用的CP系统有etcd和consul, 通用的对立面就是专用系统. 所以在某些场合是有这种需求的. 然而etcd embed的可用性极差, Windows上面跑会出现各种问题, 而且不能定制协议, ...
- Raft——可理解的分布式一致性算法
Raft Understandable Distributed Consensus http://thesecretlivesofdata.com/raft/ 一个直观的动画,便于理解raft算法. ...
- 30分钟带你理解 Raft 算法
为什么需要 Raft? Raft 是什么? Raft 的目标 前置条件:复制状态机 Raft 基础 Leader 选举(选举安全特性) 日志复制(Leader只附加.日志匹配) 安全 学习资料 使用 ...
- Go多组Raft库
Go多组Raft库 https://github.com/lni/dragonboat/blob/master/README.CHS.md 使用用例 https://github.com/lni/dr ...
- [搜狐科技]由浅入深理解Raft协议
由浅入深理解Raft协议 2017-10-16 12:12操作系统/设计 0 - Raft协议和Paxos的因缘 读过Raft论文<In Search of an Understandable ...
- 理解Raft协议
目录 1.Paxos算法存在的问题 2.Raft算法 2.1 复制状态机 2.2. Raft算法 2.2.1 安全性问题 2.2.2 Leader选举 2.2. ...
- 【转】分布式一致性算法:Raft 算法(Raft 论文翻译)
编者按:这篇文章来自简书的一个位博主Jeffbond,读了好几遍,翻译的质量比较高,原文链接:分布式一致性算法:Raft 算法(Raft 论文翻译),版权一切归原译者. 同时,第6部分的集群成员变更读 ...
- 借助TZImagePickerController三方库理解自定义相册
借助TZImagePickerController三方库理解自定义相册 1.整体架构分析 整体框架大致可以分为几个部分 <1>工具类-TZImageManager:这个类主要是工作是提供一 ...
- In Search of an Understandable Consensus Algorithm" (https://raft.github.io/raft.pdf) by Diego Ongaro and John Ousterhout.
In Search of an Understandable Consensus Algorithm" (https://raft.github.io/raft.pdf) by Diego ...
随机推荐
- 农场派对(party)(信息学奥赛一本通 1497)
[题目描述] N(1≤N≤1000)头牛要去参加一场在编号为 x(1≤x≤N) 的牛的农场举行的派对.有 M(1≤M≤100000) 条有向道路,每条路长 Ti(1≤Ti≤100):每头牛都必须参加完 ...
- mysql下sql语句令某字段值等于原值加上一个字符串
MYSQL在一个字段值后面加字符串,如下: member 表名 card 字段名 update member SET card = '00' || card; (postgreSQL 用 || 来连贯 ...
- chown与chmod的区别
chown 修改文件和文件夹的用户和用户组属性 1.要修改文件hh.c的所有者.修改为sakia的这个用户所有 chown sakia hh.c 这样就把hh.c的用户访问权限应用到sakia作为所有 ...
- Swarm容器集群管理(超详细)
一.Swarm介绍 Swarm是Docker公司自研发的容器集群管理系统, Swarm在早期是作为一个独立服务存在, 在Docker Engine v1.12中集成了Swarm的集群管理和编排功能.可 ...
- Server 2003 操作系统位数
安装好电脑系统,如何查看windows 2003/xp/win7是64位还是32位? 方法/步骤 第一种方法:桌面上鼠标右键单击“计算机”(我的电脑) 在弹出的快捷菜单中选择“属性”,如果看到64的字 ...
- [源码分析]LinkedHashMap
一个键有序的 HashMap 可以将 LinkedHashMap 理解为 LinkList + HashMap,所以研究LinkedHashMap之前要先看HashMap代码.这里不再赘述.其实L ...
- curl抓取页面时遇到重定向的解决方法
用php的curl抓取网页遇到了问题,为阐述方便,将代码简化如下: <?php function curlGet($url) { $ch = curl_init(); curl_setopt($ ...
- 详解intent和intentfilter
1.Intent对象简介 Intent中文意思指"意图",按照Android的设计理念,Android使用Intent来封装程序的"调用意图",不管启动Acti ...
- docker中mysql pxc集群
PXC集群 https://hub.docker.com/r/percona/percona-xtradb-cluster 安装PXC镜像 下载镜像或者导入本地镜像 docker pull perco ...
- 关于/r与/n 以及 /r/n 的区别总结
应该说还是区别的,\r就是回到行首,\n就是到下一行的,但是一般我们输出程序时,看不到明显的差别的 '\r'是回车,'\n'是换行,前者使光标到行首,后者使光标下移一格.通常用的Enter是两个加起来 ...