赞
踩
之前参加了字节青训营后端进阶班,我们选择的题目是搜索引擎,临近ddl,故有此文。
GoDance是一款用go语言编写的分布式搜索引擎,同时也是一款分布式文档数据库。支持分布式搜索以及分布式存储功能,对外提供restful Api接口来操作GoDance。
简述:集群模块整体采用了raft算法来保证分布式共识和集群高可用,借鉴了ES的思路来实现自动发现功能。
MasterNode
DataNode
一个节点可以既是MasterNode,也可以是DataNode,但建议在配置时分开部署
state状态
前三者是raft算法中的角色
term任期
节点每次选举都会把任期自增,一个节点在一个任期内只能投一票。任期也会影响到后面的选举规则。
任期的最大作用就是保证任何一个节点在一个任期内仅会投出一票,保证在“大多数”设置合理的情况下,一个任期最多只会有一个Leader被选出,从而避免脑裂现象
简述:raft算法通过“大多数"来保证集群中只有一个Leader存在,避免脑裂情况产生
基本规则
选举时机
投票策略
选举成功条件
当一个候选者向其他节点发起拉票,收到“大多数”选票后晋升为新一任的Leader
其他规则
PS:以下一些规则是自己加的,并不完全是raft算法
当一个Leader收到比自己任期高的节点心跳时,由Leader退为Follower,避免脑裂
当其他候选者接受到来自任期大于等于自身的节点心跳时停止选举,变为Follwer
当收到其他节点的拉票请求时,重置超时时间,避免再次开始选举
选举失败后,应该检查自身是否收到了新的符合合规心跳(term>=自己),如果收到则退出选举状态为Follower;如果没有则等待随机超时时间再开始下一次选举
随机超时时间
为了方便理解上述的设计方案,这里举例几个常见的分布式场景
PS:以下示例默认所有节点都是Master节点(raft算法中没有这种概念,这个是为了后面自动发现而增加的)
Leader节点定时向其他节点进行RPC调用(心跳)告诉各个从节点——“我还活着,不要造反!”
当Follower节点正常收到该心跳时,在接下来一段时间内不会进行选举操作,同时会返回当前的日志状态(最新提交和最新增加的日志Id)
接下来一种情况就是Leader宕机
当Leader宕机时会,每个节点随机时间后开始选举,如果时间过近,可能会出现同时有多个节点参与竞选,由于同一任期内一个结点只能投一票的规则,此时会发生瓜分选票的情况,导致没有节点能够达到大多数选票。
为了避免这种情况,每个节点在选举失败后,会休息随机时间再尝试选举,同时term++,最终会有一个Leader节点成功竞选。
而这两个随机时间——跟随者等待领导者心跳信息超时的时间间隔和重新选举等待时间,都是为了避免这种冲突,加快Leader选举而设立的。
当然上述是一种比较复杂的情况,真实场景中可能第一次就能选举成功,不必等待。
除了正常宕机外,还有一种情况会触发选举,那就是网络时延
因为网络时延而不能预期收到心跳的节点会开始选举,任期+1,当收到大多数选票时,成为新的Leader。
注意在这种情况下,可能会出现短暂的脑裂现象,但是并不会对集群状态产生影响,因为在这期间集群的状态修改,只有新Leader才可能达到大多数条件从而修改集群状态。
当然在这种情况下,还有一些比较复杂的情景,比如:
这里又可以分为两种情况,Leader分在多数和Leader分在少数
要注意的是Leader可能并不是原来节点,这取决于分区恢复后日志完整性,以及当时的网络情况。具体情况可以参照情景三
可以看到,无论是否发生了网络分区,集群的状态都能达到一致。
为了保证搜索引擎正常的分布搜索和分布式存储能力,集群中的元数据便必须得是一致且安全的。而集群状态机就是指该集群状态。
该集群状态保存有以下信息:
通常情况下,集群状态机会维护在内存中一个数据结构中。
每隔一段时间会讲该状态机持久化到硬盘中,便于宕机后快速恢复。
每个节点都会有一份集群元数据,便于对web层发来的请求做出决策。
在raft算法中,存储集群状态的并不是刚才讲的集群状态机,而是日志。这是因为在分布式场景中,如果要做到分布式一致性以及较少的传输损耗,追加写入的日志将会是分布式共识的不二之选。
参考raft算法,我们也是根据其设计了相应的机制
tranlog真实含义是操作日志,也就是状态机变更的操作,也就是说我们需要归纳出设计集群状态变更的几种操作
关键字段包括:
简单来说,该方案设计采用了二阶段提交的思路,不过根据自己的实现对其做了优化
第一阶段(prepared):
第二阶段(commit):
PS:这个和原来raft算法的不太一样
确认阶段由心跳RPC去进行日志同步的操作,具体步骤如下:
如果认真阅读上述设计方案,可能会有以下几个疑问:
为什么只计算MaterNode节点作为大多数判断?
因为我们要保证的大多数节点更新后,最新的日志总占大多数。
而这点就是为了确保新一轮选举,选出的Leader一定会是日志完整性最高的节点(不给日志完整性低的节点投票)。而参与竞选的正是MasterNode,所以我们只需对MasterNode计数即可。
如何判断日志一致?
判断一致是根据最新提交日志id和最新增加的日志id的二元组来决定的,两者都一致则意味着日志是一致的
为什么日志复制时要验证term?
为了保证自身节点日志完整度小于投票的节点
提交状态如何保证大多数?
上述机制能保证——如果主节点提交,那么大多数节点则有该日志。但是该机制无法保证选出来的领导者能保证提交日志的大多数。
试想这么一种情况:Leader在提交日志时,突然宕机,那么新选出来的领导者是有该日志的,但是没有提交,可目前已经有节点进行了提交,这肯定是不行。
领导者选举能保证新领导者一定包含这条committed的日志项,但这条日志项在新领导者上处于未提交状态,这时新领导者,需要尝试从最新日志项开始,向其他节点复制日志,如果,某条uncommitted的日志项,被发现已经成功复制到大多数节点上,或者发现该日志已经被提交,这时这条日志项将处于提交状态,并被应用到状态机,通知其他节点提交这条日志。
但还是有一种极其特殊的情况:假设有A B C D E五个节点,A B C成功复制了最新的日志项,leader节点A提交了并返回成功,B C未提交,这时leader节点A挂了,选举出新leader节点B,这时候只能发现2个节点具备某条uncommitted的日志项,不满足大多数。这样尽管Leader提交了,但是其他节点并没有提交同时也满足不了大多数条件,所以在新的Leader看来该日志没有被提交。
而当Leader节点恢复时发现最新的日志是提交状态,所以在日志恢复时,需要做一步额外的验证——检查自己最新的日志是否提交,如果提交则需检查该日志是否在集群也提交了(如果该日志在集群中已经不存在了,则直接复制Leader的state并持久化)
在日志提交时,我们需要将tranlog加载到集群状态机中,来影响真正的集群元数据。
要注意的是删除节点的操作有些需要注意的地方:
非Master节点直接移除出cluster状态,Master节点则采取假删的策略,修改节点状态为Dead
这样的好处就是依然会对Master节点发送心跳,而不至于一时的网络延时导致其中一个MasterNode开始无限竞选(其中一个节点因为网络延时开始竞选,但此时其他节点又接受到Leader的日志更新,根据不给日志完整性低的节点投票的原则,网络时延的那个节点会不停选举,但始终不能竞选成功,而集群状态又失去了该节点联系,所以会导致该节点丢失)
不同的指令意味着不同操作,在此就不赘述了。
考虑到极端情况,当日志复制成功后,如果节点宕机,则会丢失tranlog,这样就不能保证大多数节点拥有完整性最高的日志。
所以在日志复制时会直接将增加的日志进行持久化,追加写入到日志文件当中。
一些优化
为了节省资源,这里只有MasterNode才会持久化日志,因为非MasterNode不会参与竞选,他们的日志恢复强依赖于Leader节点
当发现日志差异过大时,这意味着一项项复制日志性能损耗太大,此时会选择直接复制集群元数据并持久化的方式来进行同步
当集群状态机进行持久化之后,那么这之前的日志都可以不必保存,之前写入的文件便可以删除。之后的日志可以追加写入一个新的文件
以上采用raft算法解决的集群间的分布式共识问题,但是并没有解决初始的选举问题。换句话讲,上述集群选举机制都是在各个节点彼此知道各自的存在的情况下进行的,但是在真实情况下,各个节点一开始是不知道彼此的存在的,那么如何解决这个问题呢?
这里借鉴了ES的设计思路,引入种子节点的概念。
什么是种子节点?
种子节点可以在配置文件中配置,它是一系列ip地址的集合。
要注意,种子节点必须要保证其可用,不然种子节点挂了就无法进行自动节点发现了
自动发现机制
在节点上线时,会先从种子节点获取当前的集群状态。
如果当前有Leader节点,则会向Leader节点发送加入的RPC。
如果当前没有Leader节点,MasterNode则会开始竞选流程,向其他节点发起拉票RPC,而拉票的对象就包括种子节点;非Master节点则会轮询种子节点,直到当前集群成功出现了一个Leader。
这里暂时不进行讲解了,详情请看GoDance代码仓库中的cluster模块。
该模块在陆续完善中,未来将实现一个真正可以运行的分布式搜索引擎,敬请期待。
说实话实现raft算法真的有点复杂,尤其是细节部分。在网上搜集的各种资料质量参差不齐,看论文又太煎熬。很多细节都靠猜,靠自己尝试去补全细节,但很多时候自己去尝试推演发现有问题,于是又是各种搜索…最终也算实现了raft算法,自己的推演也终于跑通,之后应该会再写一篇,总结一下这个过程中遇到问题和一些自己的思考。
愿我们以梦为马,不负人生韶华!
与君共勉!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。