关于共识算法Raft的常见误解
Raft 共识算法
最近翻了翻Raft相关的资料,同时也总结了日常工作的一些积累,就当做Raft技术笔记吧。
由于工作的关系,Raft是所有组件共用的算法核心,包括RocketMQ、Consul、CubeFS等,所以对Raft也算脸熟了(当然它可能不怎么认识我,工作中这种情况挺常见的,不知道为什么:-)
最终一致性与线性一致性
最终一致性,常表述为弱一致性,这里的弱是较于强而言(后续会有个人基于现实场景中遇到的问题进行对比),而线性一致性常说的是强一致性,要求在相同的情况下还要符合全局时钟的先后顺序。
此外还有顺序一致性和因果一致性。
顺序一致性弱于线性一致性,强调进程间操作的执行顺序(而非全局时钟),极有可能在全局时钟看来结果是错误的但是仍然满足顺序一致性,总的来说顺序一致性要求顺序一致不管对错。
因果一致性则是弱一致性,强调因果关系;如果A能推导出B,B能推导出C,那么A一定能推导出C;
if A => B,B=>C;那么A=>C。
总的来说线性一致性>顺序一致性>因果一致性>最终一致性,当然实现难度也是同理;
上述描述都比较晦涩,结合实际项目说明,比如Zookeeper实现的就是顺序一致性(严谨地说,写操作是线性一致性,读操作是顺序一致性),保证在所有节点上读请求一定在写入请求之后执行;而ETCD、RocketMQ都是借助Raft实现了线性一致性(当然Raft与Paxos亦有差别)。
而最终一致性的代表就是Consul,简单介绍Consul的Gossip协议,相信对最终一致性就会有直观的了解。
Gossip协议是当集群中某个节点收到消息时,随机选取集群中几个节点传播消息,收到消息的节点亦如此;按照这个设计,信息在集群中如同八卦一般,一传十,十传百,直到集群中所有节点都“知晓”。
但是实际工程实践中我们发现速度远比想象的慢,比如网络会极大影响传播速度,集群拓扑的路径选择等;所以部署Consul时往往会选择几个Leader节点,读写都在Leader上,这样只要保证了有限几个leader节点之间的传播不受影响即可。
想起刚接手Consul维护工作时,遇到的一个问题;
业务将车辆下线服务注册到Consul成功后(某个Server节点,集群呈网状部署,都是单点,是的单点!!!),此时按照实际应该通过集群广播将信息扩散到其他节点(包括其他Leader节点和Follower节点),但是由于此时该节点所在百度云宿主机(额,是的,我们用的百度云BCC :-)网络异常,于是扩散终止了。
但是其他Server依然在对外提供服务,于是其他服务完全没有感知到有新车下线的消息,整个车云链路就整体“宕机”了。
后来排查发现了此问题,推动业务将服务之间的调用链路通过MQ进行结耦,全面清查单点问题,同时Consul的注册机制调整为多数成功才算成功。
另外此事还有一个后续就是故障宿主机恢复后,由于该Server节点落后数据太多,于是其他Server上的信息如潮水般涌来,导致故障的Server节点在恢复后短时间内大量同步其他节点的数据,CPU持续拉高,而其自身的数据始终不能及时同步出去,变成“只吃不吐”(于是熬夜打补丁增加了限流逻辑,又是一个通宵:-).
日志的覆盖与删除
摘抄网上一篇文章的片段“由于从节点的最大日志数据二元组是<7,12>,与leader发送过来的日志数据<6,10>不匹配,索引11、12的数据将被删除”
Raft 主从同步流程如下:
索引6-10会从leader同步(Append Entries),但是由于leader的索引只是到10,follower上的committed index 会重置到10(与Leader保持一致,参考Raft 安全性原则),索引11、12不会做任何更改,当leader收到新的写请求后索引递增到11、12;那么follower会从leader同步数据,此时会覆盖(follower上索引11、12的内容会被leader上新写入的内容覆盖,由此leader、follower上索引11、12保持一致);
上述流程中可以发现,并没有删除流程;Raft 的读写都从Leader上进行,同时Leader是Append-Only的,所以删除流程对于Raft来说是不存在的操作。
Remove节点时需要skip
回放时需要跳过remove自身节点的日志,否则当前节点无法加入集群;
这点尤为重要,曾经线上遇到某个节点恢复上线时总是保持分钟级在线,然后就自动下线了,四处排查总是找不到原因,本地也无法复现,最后滤逻辑和日志发现,remove self了,哭笑不得。
总结
未完待续