笔者很久以前就了解过raft了,它相比paxos实现跟逻辑简单了许多,因此市场上有很多的分布式系统都是使用raft协议来保证数据的一致性的。不过第一个实际接触到的raft应用倒是mongo的副本集了。
所以今天就从mongo的副本集说起。通常有多少个副本集,就保存有多少份数据。多份数据,就需要通过复制的手段来实现。
这里复制的目的并不是为了读写分离,而是提高了数据的高可用。多份数据为了保证最终一致性,mongo底层是用一种变型的raft来实现的。
一、raft的介绍
1.1 问题所在
如果是单节点,客户端修改某个值,成功的话整个系统都变为某个值:
ok!一个节点,我自己跟自己达成共识即可。
如果像mongo副本集,有多个节点存储同份数据,那就麻烦了:
同一个客户端同时向两个节点发送更新数据的命令,结果节点1成功执行命令,节点2因为网络隔离无法正确执行:
这个时候,节点1的值为1,节点2的值还是未设置。那么读的时候,我们根本无法知道哪个节点上的值才是真正的值。
当当当,这时候raft出现了。
它能提供一种解决这种多个节点数据不一致的问题。
1.2、解决之道
怎么样让两个节点上面的数据是一致的?
如果两个节点初始状态一致,然后在两个节点上面执行一样的操作,最终两个节点的数据肯定是一致的。因此:
一样的初始状态+同样的操作=数据一致
让所有节点的初始状态一致并不困难。困难在于如何执行一样的操作。raft的出现就是为了保证多个节点能够执行同样的操作。
raft的实现就是从多个节点中选举出一个leader,由这个leader来指挥,让其他所有的节点做一样的操作。
1.3、选举
选举的目的,是从所有可用节点中选举出一个master节点来为client提供服务的。
raft假定节点有三种角色:follower、candidate和leader。
follower:leader执行什么,我就执行什么,所以叫做follower。
candidate:准备当leader的follower,只有当candidate获得大多数人的投票。它才能从candidate晋升为leader。
在最开始的时候,所有节点都是follower状态。
follower时时刻刻跟leader保持一个心跳链接。当一次心跳到达的时候,follower就重置定时器,将失联的时间设置为x+随机毫秒时间后。这个时间也称之为超时时间。
因为在初始状态下,所有节点都是follower,没有leader。这个时候,所有的follower在接下来的随机毫秒时间内,都会进入失联状态。
一旦follower进入失联状态,这个follower立马推动选举。因为之前没有任何选举存在,因此这是第一个选举,它默默记下1并且给自己投了一票,然后邀请其他所有的follower来投票。
这个时候,剩下两个follower因为随机定时,可能还没有进去失联状态。所以它们就会给第一个节点投票。因此第一个节点得到3票,超过大多数票(2),它成功晋升为leader了。
成为leader之后,客户端所有的操作都得先经过leader节点。
至此,选举完成。
而选举的发动时机通常是:
Follower在一段时间内(选举超时election timeout)收不到Leader发送的心跳消息
Leader在一段时间内(心跳超时heartbeat timeout)联系不上大多数的Follower
1.4、日志复制
现在,这个leader是负责接收client的请求,请求改变的命令会记到节点的日志中,这个时候这个命令是未提交的,不会更改到系统实际上的值。
接着这个日志就会复制到另外几个节点中,然后leader会等收到大多数节点回复,表示都写到他们的日志中。
然后这个日志就会在leader上提交,然后就通知follower节点也可以提交。
这样他们就达成了共识了。
只要大多数节点都commit成功,那么这个写操作就表示ok了。
至此,大多数节点的值都为1了。达成了共识。
1.5、脑裂情况
如果有5个节点,因为网络分区的原因。导致分成两个区,一个三个节点,一个两个节点。这两个区会分别选举出一个leader出来。
不过当日志提交的时候需要得到大多数节点的提交,因此其中一个是无法提交的。因此raft也是能够解决脑裂的问题。
1.6、常见问题
raft vs paxos
相对来说,raft比paxos实现跟逻辑上要简单许多。下次有时间讲讲paxos,再来详细对比一下。
raft VS 2PC、3PC
目的不一样。raft、paxos这类通常是用来保证数据的一致性的。而2PC、3PC是为了保证操作的原子性。都是分布式的一种技术实现。
1.7、总而言之言而总之
上面的介绍只能说是raft的入门,实现一个raft其实还是有很多细节需要处理的。比如选举的规则,日志的存储,日志的同步是push还是pull,心跳的实现,操作回滚等等。如果是做应用层的,我个人感觉raft入个门应该就ok,如果是做分布式基础服务的,最好就能做出个玩具raft。
更多php+golang文章,欢迎关注公众号:编程说。
有疑问加站长微信联系(非本文作者)