raft协议学习
项目介绍
本文档主要是基于git项目的代码进行学习。
我已将全部的代码上传至https://github.com/yeseary/raft-example,可以直接下载运行。
项目实现了一个分布式的KV存储系统,基于raft协议进行一致性保证。
项目的功能包括:
- key值增删改操作
- 状态查询和管理,包括查询leader、集群状态、新加节点操作
- 并基于此增加了web层,通过http请求进行操作。
本项目在原有的项目的基础上,新加的功能为:
- 增加代码注释,方便理解
- 支持集群的状态查询,包括机器列表和leader地址
- 新增了快照Snapshot的实际实现,保证了数据的持久性存储
- benchmark测试代码,包括key的写入、读取和删除,并支持简单的容灾处理
raft协议
raft协议是分布式一致性算法,paxos协议的简化版本,用于解决分布式系统中的数据一致性问题。
raft协议中包含三种角色:leader、follower、candidate。
其中leader负责处理所有客户端的请求,follower和candidate负责响应leader的请求。
leader和follower之间通过心跳包进行通信,leader会周期性的向follower发送心跳包,如果follower在一定时间内没有收到leader的心跳包,则认为leader已经挂掉,然后follower会发起选举,选举出新的leader。链接
raft库
市面上有etcd和consul两种实现,consul的底层库附带了网络层的实现,不需要自己实现网络相关,实现起来更加方便。
具体使用
启动web服务
我们构建三个节点,分别为:
节点名称 | raft地址 | 服务地址 |
---|---|---|
node1 | localhost:2222 | localhost:8222 |
node2 | localhost:2223 | localhost:8223 |
node3 | localhost:2224 | localhost:8224 |
以node1为例,启动命令:
./raft-example --node-id node1 --raft-port 2222 --http-port 8222
构建集群
需要设置node1,此时有node2和node3需要加入集群
# 通知node1,node2加入
curl 'localhost:8222/join?followerAddr=localhost:2223&followerId=node2'
# 通知node1,node3加入
curl 'localhost:8222/join?followerAddr=localhost:2224&followerId=node3'
查看集群状态
curl 'http://localhost:8222/state'
返回结果:
{
"leader": {
"addr": "127.0.0.1:2222",
"id": "node1"
},
"index": 32111,
"nodes": [{
"addr": "127.0.0.1:2222",
"id": "node1"
},
{
"addr": "localhost:2223",
"id": "node2"
},
{
"addr": "localhost:2224",
"id": "node3"
}]
}
开始进行key-value操作
通过服务接口进行分布式的KV操作:
- kv的写入操作,需要通过leader来发起,因为涉及到数据的变更,需要调用Apply方法,只能在leader上发起
- kv的读取操作,如果从leader读取,可以保证是最新的值,如果从follower读取,可能有一定概率的延迟
# 写入1万个key
go test -bench=BenchmarkKeySet -benchtime=10000x
# 读取1万个key
go test -bench=BenchmarkKeyGet -benchtime=10000x
# 删除1万key
go test -bench=BenchmarkKeyDelete -benchtime=10000x
容灾处理
当在写入或删除key的时候,随机杀掉一个节点服务:
- 如果kill的是follower,不影响业务,因为写入操作需要从leader发起
- 如果kill的是leader,则raft会重新发起选举,直到选出新的leader
如上图,一开始的leader是2223,当杀掉2223节点的时候,set-key接口会进行报错。此时业务重新获取state接口,获取新的leader。