在分布式系统开发中,Go 语言因其原生支持并发(Goroutine 和 Channel)而成为实现共识算法的首选语言。
要理解 Raft,我们得先从宏 观的理论基础开始。
一、理论基石:CAP 定理
CAP 定理指出,在一个分布式系统中,以下三个特性不可兼得,最多只能同时满足两个:
-
Consistency(一致性): 每次读取都能读到最新写入的数据。
-
Availability(可用性): 每次请求都能得到响应(不保证是最新的)。
-
Partition Tolerance(分区容错性): 即使节点之间的网络断开(分区),系统仍能运行。
在分布式系统中,P 是必须保证的。 因此,我们通常在 CP(侧重强一致性,如 Etcd/Consul)和 AP(侧重可用性,如 Cassandra/Eureka)之间做权衡。Raft 属于 CP 协议。
二、一致性原理
一致性指的是系统中所有副本的数据状态相同。分布式系统实现一致性通常靠 复制协议,比如:
-
主从复制(Leader + Follower)
-
共识算法(Consensus Algorithm):保证即使部分节点宕机,系统仍能达成一致。
常用的共识算法:
-
Paxos:经典算法,但实现复杂。
-
Raft:理解简单,工程上更常用(etcd/k8s 用 Raft)。
三、什么是 Raft?
Raft 是一种为了管理复制日志的 共识算法 。它的设计目标是 易于理解 。它将一致性问题分解为三个子问题:
1. 领导选举 (Leader Election): 当现有领导者失效时,选出新的领导者。
2. 日志复制 (Log Replication): 领导者接收客户端指令,并同步到其他节点。
3. 安全性 (Safety): 确保如果任何节点应用了某个日志条目,则其他节点不会在该索引处应用不同的条目。
四、简化实现一个Raft
- 定义节点状态
go
type State int
const (
Follower State = iota //追随者
Candidate //候选人
Leader // 领导
)
type Raft struct {
mu sync.Mutex
me int // 节点 ID
currentTerm int // 当前任期
votedFor int // 给谁投了票
state State // 当前角色
// 计时器
electionTimer *time.Timer
}
- 选举逻辑(心跳与超时)
go
func (rf *Raft) ticker() {
for {
select {
case <-rf.electionTimer.C:
rf.mu.Lock()
// 超时了,变身为候选人发起投票
rf.state = Candidate
rf.currentTerm++
rf.votedFor = rf.me
rf.resetTimer() // 随机化超时时间,防止"选票瓜分"
go rf.startElection()
rf.mu.Unlock()
}
}
}
- 处理投票请求 (RequestVote)
cs
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
// 如果对方任期比我小,拒绝投票
if args.Term < rf.currentTerm {
reply.VoteGranted = false
return
}
// 如果对方任期大,我变回 Follower
if args.Term > rf.currentTerm {
rf.currentTerm = args.Term
rf.state = Follower
rf.votedFor = -1
}
// 投票逻辑
if rf.votedFor == -1 || rf.votedFor == args.CandidateId {
rf.votedFor = args.CandidateId
reply.VoteGranted = true
rf.resetTimer() // 投了票,刷新自己的计时器
}
}
五、为什么Go适合写Raft?
-
RPC 框架: Go 内置的
net/rpc或 gRPC 可以非常方便地实现节点间的通信。 -
Select/Timer: Raft 大量依赖超时控制。Go 的
select配合time.Timer能优雅地处理"竞态超时"。 -
状态机: 借助于 Channel,可以很方便地将已提交的日志推送到应用层状态机。
友情链接:加班费计算器(vx小程序搜索"加班计")
*源码地址*
评论区要
如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!