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

在分布式系统开发中,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. 定义节点状态
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
}
  1. 选举逻辑(心跳与超时)
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()
        }
    }
}
  1. 处理投票请求 (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小程序搜索"加班计")


*源码地址*

评论区要


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

相关推荐
Wang's Blog38 分钟前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx
2601_962071571 小时前
类变量和全局变量的查找路径有什么区别?
开发语言·python
\光辉岁月/2 小时前
5.java-数组
java·开发语言
Java后端的Ai之路2 小时前
Python进阶探索29_eval内置函数
开发语言·python·探索·eval·内置函数
谢亮_vipxieliang2 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
时间的拾荒人3 小时前
Qt 多线程详解:从 QThread 到实战
开发语言·qt·面试
郑州光合科技余经理3 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
2601_962071573 小时前
类变量和全局变量的生命周期有什么区别?
java·开发语言·jvm
code2cat3 小时前
Java进阶篇之AtomicReference:一次替换引用,读到一份完整状态
java·并发编程·不可变对象·atomicreference
专业程序开发源3 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计