结合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 ...
随机推荐
- javascript中的indexOf与if判断的装逼写法
常规indexOf与if判断写法: if("112233".indexOf("22")>-1) { console.log("很二") ...
- Luogu3379 【模板】最近公共祖先(LCA)
题面 题解 这里讲一种硬核做法. 首先\(\mathrm{dfs}\)整棵树,求出这棵树的欧拉序,然后\(\mathrm{LCA}\)问题就变成了\(\pm 1\mathrm{RMQ}\)问题. 考虑 ...
- idea在使用git clone 时出现Filename too long
idea在使用git clone 时出现Filename too long的报错信息,使用如下命令就可以解决该问题:在 git bash命令模式下,运行命令 git config --global c ...
- TICK/TIGK运维栈安装运行 docker-compose【下】
InfluxDB 构建Dockerfilevim /opt/influxdb-docker/Dockerfile FROM influxdb COPY influxdb.conf /etc/influ ...
- vs2015编译OBS-Studio21.1.12
原文地址:http://www.freesion.com/article/37445100/ 参考:https://blog.csdn.net/su_vast/article/details/7498 ...
- IDEA把spring-boot项目打包成jar
1.打开项目,然后右击项目选中‘Open Module Settings’进入project Structure( 快捷键 Ctrl+Shift+Alt+S或者File->Project Str ...
- EasyNVR摄像机网页Chrome无插件视频播放功能二次开发之通道配置文件上传下载示例代码
背景需求 熟悉EasyNVR产品的朋友们都知道,产品设计初期根据整个直播流程层级,我们将EasyNVR无插件直播系统划分为:硬件层.能力层.应用层,连接硬件与应用之间的桥梁,同时屏蔽各种厂家硬件的不同 ...
- windwos 安装 vue-cli
安装vue-cli 安装之前我们需要先安装node.js以及包管理工具npm,有兴趣的可以安装nvm版本管理工具 地址:https://www.cnblogs.com/lph970417/p/1184 ...
- 说一说ORACLE的监听
1.讲在前面 本文档仅适用于如下范围:Oracle以dedicate (专有)连接模式通过TCP/IP协议连接的场景. 2.监听的作用 在谈监听的作用之前,有必要先看看监听的工作原理图: 客户端进程发 ...
- Appium元素定位难点:混合式的native+webview
现在大部分app都是混合式的native+webview,对应native上的元素通过uiautomatorviewer很容易定位到,webview上的元素就无法识别了. 1.认识识webview & ...