ETCD源码分析(三)Raft协议入门

本文通过一个简单的并发编程场景,解释了分布式一致性算法Raft的工作原理,将复杂的理论转化为易于理解的故事。Raft协议确保数据在分布式系统中的安全性和一致性,文章通过比喻说明了Raft在选举、日志复制和共识达成等方面的角色,并指出Raft并不涉及具体的通信协议和数据存储实现。这种从应用角度出发的学习方法有助于更好地理解和应用一致性算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

适合阅读本文的人群

  1. 对Raft协议感兴趣的人
  2. 看过一些Raft相关介绍的博文,但是看不懂的(比如我)

很多Raft相关的分析文章,或者是著名的《In Search of an Understandable Consensus Algorithm》 论文,都会把下面的一张图放出来:

截屏2021-09-27 16.33.33.png

再介绍选主日志复制状态机等一系列概念,恕我直言,真是看的人昏昏欲睡。也许有的表述确实看懂了,可正如古人所说,纸上得来终觉浅,要知此事需躬行。Raft协议跟我们日常写代码有什么关系,我们到底能不能用上它呢?

我个人觉得,看博文(看介绍)–> 理解Raft --> 理解ETCD是怎么使用Raft的 --> 把Raft应用到日常开发,这条学习路径未免太长太枯燥,不如换个思路,把这条路径反过来学习,或许更能坚持下去。

让我们先沉浸在日常的一个最普通的场景:

func main(){
	var d HashSet
	d[1] = 2
	for (每个 k,v) in d{
		print(k,v)
	}
}

几行伪代码,作用也很简单,声明一个HashSet,然后写入一个key、value,然后遍历这个Set 打印出来,一切都是那么的简单易懂。

func main(){
	var d HashSet
	{
		// 线程1
		d[1] = 2
	}
	{
		// 线程2
		d[1] = 3
	}
	for (每个 k,v) in d{
		print(k,v)
	}
}

但是人嘛,总是贪心不足的,总想尽可能压榨机器性能,于是有了线程的概念。上面的代码有基础的同学应该能看出来,线程1 和线程2 在并发修改HashSet,必须加以控制,否则d[1]有可能是2也有可能是3,加控制无非就是锁、并发安全的HashSet等手段,虽然让代码变长且影响执行效率,但也还能忍受。

// 进程1
func main(){
	var d HashSet
	d[1] = 2
	
	for (每个 k,v) in d{
		print(k,v)
	}
}
// 进程2
func main(){
	var d HashSet
	d[1] = 3
	
	for (每个 k,v) in d{
		print(k,v)
	}
}

时间再到了现在,线程已经不能满足贪婪的人类了, 于是我们加了更多的机器,在不同的机器上跑同样的程序,来提高性能。这时问题又升级了,虽然大家的HashSet都叫d,但显然,进程1上塞到HashSet的数据,进程2是不知道的,反之亦然。

那怎么解决呢,显然,我们要引入一个第三方服务,把HashSet放到它那里,而我们只要做个“老板”,对它发号施令就行了。它会保证:

  1. 只要数据被它接收且确认,就不会丢失
  2. 随时都可以读取以前的数据

老话说:鸡蛋不能放在同一个篮子里,所以我们还得要求这个第三方服务要把数据复制成多份,分开存储。所以,整个流程就变成了这样一个故事:

  1. 一天,作为老板的你,向自己的秘书长下达一个指示。
  2. 秘书长马上召集所有秘书开会,要求把领导指示记在自己的笔记本上,并深刻的讨论,最后大家达成共识,老板说的对!
  3. 秘书长把会议结果写成会议纪要。
  4. 秘书长把会议纪要发给你看,你看到自己的指示一字不差的出现在纪要中,欣慰的笑了。

截屏2021-09-27 16.33.33.png
好,现在在回头看这张图,对比之下很好理解了,Client就是老板,Consensus Module 就是秘书处(一个秘书头子和几个小秘书),Log就是秘书们的笔记本,State Machine就是会议纪要。那Raft协议在哪里呢? 不难理解,开会的整个流程就是Raft协议规定好的了。😁

Raft协议假定了所有秘书是安全可信的(非拜占庭容错条件),在此框架下,处理了秘书长的选举;秘书们离职和入职流程(节点变更);规定秘书们怎么在自己的笔记本上记录(RaftLog);规定了达成共识的条件(少数服从多数制);甚至规定了秘书们发生内斗(网络分区),老板应该相信那拨人是值得信任的(多数节点>少数节点)。

那么反过来,Raft不管什么呢?首先,秘书们的通信是不管的,相当于是秘书们其实是异地办公,作为“老板”,得给它们一人发一部手机(通信协议,ETCD中是用gRPC);其次,会议纪要的存储是不管的(ETCD中,用 boltdb 来存“会议纪要”)。

所以,Raft也许离我们的生活并不遥远,就像文章开头说的,从应用的角度出发,也许才能更好的理解它,也能更好的理解所有一致性算法(Raft、ZAB、Paxos)它们到底做了什么,解决的是什么问题。

文章中的比喻也许略有不妥,请原谅作者的才疏学浅,有错误欢迎指正👏🏻。

参考资料

Raft介绍
Codedump大佬的Raft博文

### etcd 中的 Raft 共识算法源码 etcd 是一个分布式的键值存储系统,广泛用于高可用的服务发现和配置管理。etcd 实现了 Raft 一致性协议来确保数据的一致性和可靠性。 #### 源码结构概述 etcdRaft 实现在 `go.etcd.io/etcd/raft` 包中[^1]。以下是主要模块及其功能: - **raft.go**: 定义了核心的 Raft 结构体以及基本操作方法。 - **state_machine.go**: 描述了不同状态下(Follower, Candidate, Leader)的行为逻辑。 - **log.go**: 处理日志条目的追加、提交等功能。 - **node.go**: 提供了一个高层接口,封装了底层细节以便于应用集成。 #### 关键文件路径 对于想要深入研究 etcdRaft 协议的具体实现,可以从以下几个重要文件入手: - `/pkg/raft/raft.go`: 这里包含了整个 Raft 状态机的核心逻辑,包括选举、心跳检测等机制。 ```go // Node represents a node in the raft cluster. type Node struct { ... } ``` - `/pkg/raft/log.go`: 日志管理部分,负责持久化日志记录并维护副本间同步。 ```go func (l *Log) Append(entries []pb.Entry) uint64 { ... } ``` - `/pkg/raft/node.go`: 将低级别的 Raft API 抽象成易于使用的节点对象。 ```go n := NewNode(...) defer n.Stop() for msg := range n.Ready() { // handle message... } ``` 通过阅读上述提到的关键组件代码,能够更好地理解 etcd 如何利用 Go 语言特性高效地实现了 Raft 协议中的各项功能[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值