遗世紫丁香 2020-01-11
一个常见的误解就是复制到多数副本的就可以视作commited, 其实还不够。缺少必须已经执行了对应的操作这个步骤。个人理解在实际 Raft使用过程中,就是存在某个Raft log 在append到多个副本的瞬间宕机了,由于还没有执行on_apply() ,其实还没有向上层用户ACK 这条日志已经成功。
因此这条日志可以truncate, 也可以后续随着后续的log commited 之后自己也被commited。
这个比较大规则如下:
某个follower 收到 vote 请求之后,会计算出收到请求的时间和上次收到主心跳的时间间隔,然后把这个间隔和一个约定的较小时间进行比较。如果前者还小(此时说明还有其他节点长时间没有收到主的心跳,发起了vote),拒绝这次vote 请求,否则投赞成票。
考虑三个互联互通的IDC A、B、C,B是主副本,如果由于某种原因B、C网络不通了,如果没有上面限制,B发起带有最高term信息的vote请求,A会同样vote请求,这样主就从B变成了C。同理,过了以后,主又很可能从C再变成B。如此 反复循环,整个复制组上没有办法提供持续稳定的主。
这个规则如下:
某个follower 收到 vote 请求之后,在current term和请求中的term 相等的情况下:需要使用当前节点的LastLogIndex 、LastLogTerm和请求中的LastLogIndex、LastLogTerm进行比较,如果前者大,则拒绝请求;如果后者大,则同意请求。
考虑下,为啥要这样?还是考虑上面三个互联互通的IDC A、B、C,B是主副本,如果由于某种原因B、C网络不通了,C是划分节点。当网络划分之后,C的current Term是最高的,它很可能发起vote,这样把B Stepdown了,A和B的currentTerm 提升了;可是因为C的LogIndex很低,它发到A和B的Vote 请求,在同A、B的Last LogIndex? 比较时发现自己太小,而没法拿到A、B的信任票,因此Vote失败。随后A或B再发起Vote , 就是用最高的Term(C的Term+1 )、最新的LastLogIndex(A 或B的LastLogIndex),一定会选主成功,并且拥有最新的数据。
Raft采用的成员变更单策略相对简单,每次只增删一个节点,这样就不会出现两个多数集合,不会造成决议冲突的情况。按照如下规则进行处理:
按照上面的规则,可以实现安全的动态节点增删,因为节点动态调整跟Leader选举是两个并行的过程,节点需要一些宽松的检查来保证选主和AppendEntries的多数集合:
为了避免同时有两个节点变更正在进行,在有未committed的change正在进行的时候,不允许进行节点变更。节点变更有一个问题,对一个只有两个节点的Cluster,发起RemovePeer。这个时候一个节点挂掉,另外一个节点没有收到RemovePeer请求,这样系统将停止工作。因此强烈建议集群节点数>=3个。