每日一Go-71、理论知识:CAP 、一致性原理 、Raft 机制(简化实现一个 Raft)

0 阅读3分钟

在分布式系统开发中,Go 语言因其原生支持并发(Goroutine 和 Channel)而成为实现共识算法的首选语言。

要理解 Raft,我们得先从宏 观的理论基础开始。

一、理论基石:CAP 定理

CAP 定理指出,在一个分布式系统中,以下三个特性不可兼得,最多只能同时满足两个:

  • Consistency(一致性): 每次读取都能读到最新写入的数据。

  • Availability(可用性): 每次请求都能得到响应(不保证是最新的)。

  • Partition Tolerance(分区容错性): 即使节点之间的网络断开(分区),系统仍能运行。

在分布式系统中,P 是必须保证的。  因此,我们通常在 CP(侧重强一致性,如 Etcd/Consul)和 AP(侧重可用性,如 Cassandra/Eureka)之间做权衡。Raft 属于 CP 协议。

二、一致性原理

一致性指的是系统中所有副本的数据状态相同。分布式系统实现一致性通常靠 复制协议,比如:

  1. 主从复制(Leader + Follower)

  2. 共识算法(Consensus Algorithm):保证即使部分节点宕机,系统仍能达成一致。

常用的共识算法:

  • Paxos:经典算法,但实现复杂。

  • Raft:理解简单,工程上更常用(etcd/k8s 用 Raft)。

三、什么是 Raft?

Raft 是一种为了管理复制日志的 共识算法 。它的设计目标是 易于理解 。它将一致性问题分解为三个子问题:

    1.  领导选举 (Leader Election):  当现有领导者失效时,选出新的领导者。

    2.  日志复制 (Log Replication):  领导者接收客户端指令,并同步到其他节点。

    3.  安全性 (Safety):  确保如果任何节点应用了某个日志条目,则其他节点不会在该索引处应用不同的条目。

四、简化实现一个Raft

1. 定义节点状态

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
}

2. 选举逻辑(心跳与超时)

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()
        }
    }
}

3. 处理投票请求 (RequestVote)

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小程序搜索“加班计”)


*源码地址*

1、公众号“Codee君”回复“源码”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!