raft怎么解决分布式数据一致性问题的?
一句话就是:把 分布式数据一致性问题 转换为 单节点(Leader)日志持久化问题。
只要保证Leader日志存储的持久化,靠leader做日志分发来保证集群中大多数节点的数据一致性。
先介绍下用到的概念
Leader: 主节点,每个term有且只有一个
Candidate:候选主节点,选举未完成时,每个节点都可能宣城自己是候选人
Follower:从节点,从主节点同步数据并应答、参与选举投票
Term:任期,每个leader当选后会开启新的term
Leader选举
选举需要某个节点发起投票,在确定哪个节点向其他节点发起投票之前,每个节点会分配一个随机的选举超时时间(election timeout)。
在这个时间内,节点必须等待,不能成为Candidate状态。目的是避免哲学家进餐问题:防止多个节点每次都同时宣称自己是主节点并给自己投票,导致永远没有人可以当选。
现在假设有节点a、b、c:
- 初始化状态时,三个节点都是Follower状态,并且term为0
- 节点a等待160ms , 节点b等待210ms , 节点c等待200ms 。由于a的等待时间最短,所以它会最先成为Candidate,并向另外两个节点发起投票请求,希望它们能选举自己为Leader
- 这时候b、c都不是候选人,就为a投票,节点a由于得到了大多数节点的投票,最终选为Leader。
如果某个时刻,Follower不再收到Leader的消息,它就会变成Candidate。然后请求其他节点给他投票。其他节点就会回复它投票结果,如果它能得到大多数节点的投票,它就能成为新的Leader。
这是为了在leader挂掉后,集群重新选主。当然投票节点只会为数据比自己新的候选人投票(term或日志idx比自己大),否则不投票。
日志复制
假设客户端发起一个SET 1的请求:
- 这个请求会首先由leader即接收到,并且leader节点写入一条日志。由于这条日志还没被其他任何节点接收,所以它的状态是uncommitted。
- 为了提交这条日志,Leader会将这条日志通过心跳消息复制给其他的Follower节点,各节点会响应自己的写入情况。
- 一旦有大多数节点成功写入这条日志,那么Leader节点的这条日志状态就会更新为committed状态,并且值更新为1。
- leader通知其他Follower节点提交这条日志。其他节点也会将值更新为1。这个时候集群的状态是完全一致的,这个过程就是日志复制。
- 直到此时,client才会得到写成功的响应,表示这次操作已经得到了一致性保证
超时设置
选举超时
为了防止3个节点(假设集群由3个节点组成)同时发起投票,会给每个节点分配一个随机的选举超时时间,即从Follower状态成为Candidate状态需要等待的时间。
在这个时间内,节点必须等待,不能成为Candidate状态。这里的原因前面Leader选举处已经介绍过,不再赘述。
心跳超时
假设节点A和C投票给了B,所以节点B是leader节点。节点B会固定间隔时间向两个Follower节点A和C发送心跳消息,这个固定间隔时间被称为heartbeat timeout。
Follower节点一段时间收不到leader的心跳,就认为leader挂了,会发起选举。
网络分区
在发生网络分区的时候,Raft一样能保持一致性。
假设集群由5个节点组成,且节点B是Leader节点,此时发生了网络分区,节点A和B(未分区时的Leader)在一个网络分区,节点C(分区后被选为leader)、D和E在另一个网络分区。
此时一个客户端,往节点B上发送了一个SET 1,由于网络分区的原因,这个值不能被另一个网络分区中的Leader即节点C拿到,它最多只能被两个节点(节点B和C)感知到,所以它的状态是uncomitted。
另一个客户端执行SET 2的操作,它的请求打到了节点C。由于可以被同一个分区下总计三个节点(节点C、D和E)感知到,3个节点已经符合大多数节点的条件。所以,这个值的状态就是committed。
接下来,假设网络恢复正常,节点B能感知到C节点这个Leader的存在,它就会从Leader状态退回到Follower状态(原因是两个leader会比较各自的term和日志下标,较新的一个继续做leader, 另一个变为follower),并且节点A和B会回滚之前没有提交的日志(SET 1产生的uncommitted日志)。同时,节点A和B会从新的Leader节点即C节点获取最新的日志(SET 2产生的日志),从而将它们的值更新为2。此时,整个集群的5个节点数据完全一致了。
日志压缩
各个节点都会没隔一段时间或日志数量后做一次当前节点的数据快照,之后可以删除之前下标的日志,用于压缩空间。
新节点接入后,leader会把快照和后续的写日志发给他用于同步数据。