简介
Elasticsearch如此广泛流行的原因之一是因为其易于扩展,可以从仅具有几个节点的小集群扩展到有数百个节点的大型集群,并且保证不管集群规模的大小,其核心的分布式协调功能始终是稳定运行的。Elasticsearch 7.0版本开始引入了新的集群分布式协调子系统,与早期版本相比具有许多优势。本文介绍了新的协调系统有哪些改进,如何使用新的子系统,如何从6.x版本安全的升级,以及这些改进如何提高数据的一致性,最后部分描述了相关的工作原理。
Elasticsearch集群分布式协调
Elasticsearch集群可以执行许多需要多个节点参与协同工作的任务。如,将每个搜索路由到所有正确节点的分片上,以确保其结果准确无误。索引或删除某些文档时,必须更新相应节点上的每个副本。确保将每个客户端的请求从接收他的节点转发到可以处理它的节点。每个节点都维持了一份集群元信息的视图,节点根据集群元信息,执行搜索,索引和其他协调任务。这个集群元信息也称为集群状态(cluster state)。集群状态描述每个索引的映射和设置,决定每个分片分配到节点的位置,确定需要同步的分片副本数等。在整个集群中保持集群状态的一致性非常重要。很多近期发布的功能,如sequence-number、ccr,只有在集群状态一致性的保证下才能正常的工作。
协调子系统通过从节点中选举出主节点来工作。主节点会确保集群中所有的节点都能接收到集群状态的更新。这项工作是比较有挑战性的,因为像Elasticsearch这样的分布式系统必须准备好应对许多复杂的情况,如节点有时运行缓慢,Full GC导致的暂停,突然断电,网络分区,数据包丢失,高延迟时段,消息乱序等等。上述问题有可能会多个问题同时发生或间歇性的发生。即便在这样的复杂情况下,协调子系统必须依然能够保证每个节点都具有一致性的集群状态。
重要的是,Elasticsearch必须能够适应各类故障。它通过集群状态的更新必须被法定数量的节点确认这个规则来保证集群的可靠性。“法定节点数”是一个被精心选择的所有候选主节点的子集。仅要求法定节点响应状态更新请求的有点是集群的某些节点可能会在不影响集群可用性的情况下发生故障。必须仔细选择“法定节点”的集合,以确保集群不发生脑裂的情况,防止导致数据的丢失。
通常,我们建议集群有三个候选主节点,以便其中一个节点出现故障时,其他两个节点仍能安全的达成更新的法定节点数。如果集群的候选主节点数少于3个,则无法安全地容忍丢失任何节点。相反,如果集群有多余3个候选主节点,则选举和集群状态更新可能需要更长的时间。
Elasticsearch6.x及更早版本使用Zen Discovery
作为集群分布式协调子系统模块。这个子系统多年来不断地发展和成熟,成功的为各类规模的集群服务。然而,我们希望能对这个模块做进一步的改进,特别是一些涉及分布式协调如何工作的基础方面的改造。
Zen Discovery
允许用户使用discovery.zen.minimum_master_nodes
设置最少的仲裁节点数。在集群中的每个节点上正确设置此配置,并在集群节点数变化时正确更新它至关重要。系统无法检测用户是否错误配置了此设置,实际上在添加或删除节点后很容易忘记调整它。Zen Discovery
试图通过延迟选举几秒钟的时间来防止各种各样的错误配置,并且通常对其它超时也相当的保守。这意味着如果当前的主节点发生故障,在选出新的主节点之前,集群会有几秒钟不可用。而且,如果集群最终不能选出主节点,也很难分析出原因。
从Elasticserch7.0开始,我们重新设计并重写了集群协调子系统:
minimum_master_nodes
配置被移除,有利于允许Elasticsearch自己选择由哪些节点组成法定选举节点。- 典型的主节点选举可以在1s内完成。
- 增长和缩小集群变得更安全,更容易,并且错误配置导致数据丢失的机会变少了。
- 节点增加更多的记录状态的日志,帮助诊断无法加入集群或无法选举出主节点的原因。
在添加或删除节点时,Elasticsearch会自动的通过更新集群的投票配置(voting configuration)来维持最佳的容错级别。voting configuration是一组当前可以参与投票的的候选主节点。通常,voting configurations包含集群中所有符合条件的候选主节点。所有集群状态的更新都需要有voting configurations中一半以上的节点同意。由于现在交由系统来管理voting configurations,即投票的法定数量节点,即使在添加或删除节点的时候也可以避免因错误的人工配置导致的数据丢失。
如果节点无法发现当前主节点并且无法赢得选举,那么从7.0版本开始,Elasticsearch将定期记录一条警告日志,详细描述其当前的状态,以帮助诊断许多常见问题。
另外,老版本的Zen Discovery有一种非常罕见的故障模式,在Elasticsearch Resiliency Status页面上记录为“重复多次的网络分区故障可能导致集群状态更新丢失”,这个问题在新版本中已解决。
Zen2
如果您使用默认配置启动一些新安装的Elasticsearch节点,那么它们将自动寻找在同一主机上运行的其他节点,并在几秒钟后形成一个集群。如果在同一主机上启动更多节点,则默认情况下它们也会发现并加入此集群。使用Elasticsearch 7.0版本可以和早期版本一样轻松部署多个节点。
这种全自动的集群发现机制在单个物理主机上运行良好,单不够鲁棒性,无法在生产或其它严格的分布式环境中使用。这里存在很多风险,节点可能不会及时发现彼此从而形成多个独立的集群。从7.0版本开始,如果要启动在多个物理主机上启动一个全新的集群,必须指定参与第一次投票选举的候选主节点。这个过程称为集群启动引导,仅在集群第一次启动时才需要。已加入集群的节点会将投票配置存储在数据文件中,并在重启后使用这份配置。一个已经存在的集群如果新加入一个节点,可以从集群的当前主节点上接收这个配置。
启动集群时设置的cluster.initial_master_nodes
可以指定node name或IP。可以在启动命令或yml配置中指定。同时还需要指定发现子系统的相关配置以便节点可以互相发现。
如果一个新节点启动时,未设置cluster.initial_master_nodes
,那么它会尝试发现并加入现有的集群。如果节点找不到要加入的集群,那么它将定期记录一个警告日志。
master not discovered yet, this node has not previously joined a bootstrapped (v7 ) cluster,
and cluster.initial_master_nodes is empty on this node
添加新的候选主节点不需要任何特殊的步骤。只需要配置新节点发现配置,启动节点,集群将会安全自动的重新配置voting configuration。您也可以随时安全的删除节点,只要保证不同时停止一半以上的候选主节点。如果确实需要停止一半或更多的候选主节点,后者您有更复杂的扩展需求,Elasticsearch提供一个更有针对性的扩展过程,使用API直接调整voting configuration。
升级
您可以通过滚动升级或完全重启升级两种方式,将Elasticsearch集群从6.x版本升级到7.0版本.我们建议进行滚动升级,因为滚动升级过程中集群可以保持可用。在执行滚动升级到7.0版本之前,必须将6.x版本的集群升级到6.7版本。完全重启升级方式允许您从任何的6.x版本升级到7.0版本,但需要关闭整个集群。7.0版本相比于6.x版本增加了很多改动,不仅仅只是集群协调的改进。为了确保顺利升级,您应该详细的查看升级说明。
如果执行滚动升级,集群会根据节点数和老版本的minimum_master_nodes
配置自动执行集群初次启动引导。这意味着在开始升级前要正确的设置这个配置项。此处无需设置initial_master_nodes
,因为集群执行滚动升级时会自动执行初次启动引导。7.0版本的候选主节点会优先投票给6.7版本的节点,因此升级过程中6.7版本的节点会称为主节点,直到集群中所有的节点都升级为7.0版本。
如果要执行完全重启升级,则必须按上述方法设置启动引导:在重新启动集群前,必须将initial_master_nodes
设置为所有候选主节点的名字或IP地址。
在6.x版本以及更早的版本中,还有一些其他的设置允许配置Zen Discovery的行为。其中一些设置不再有任何作用,已经被删除。另外一些已经更名。已经重新命名的设置,在7.0版本中不推荐使用旧的名称,您应该调整配置来使用新的名称:
原名 | 新名 |
---|---|
discovery.zen.ping.unicast.hosts | discovery.seed_hosts |
discovery.zen.hosts_provider | discovery.seed_providers |
discovery.zen.no_master_block | cluster.no_master_block |
新的集群协调子系统包括一个新的故障检测机制。这意味着discovery.zen.fd.*
命名空间中的Zen Discovery故障检测设置不再具有任何效果。大多数用户应该使用7.0版本及更高版本中的默认故障检测配置cluster.fault_detection.*
。
数据一致性
7.0之前的Elasticsearch版本有时会让您无意中执行一系列的步骤,这些步骤会让集群的一致性处于危险的情况。相比之下,7.0及更高版本将使您充分意识到您可能正在做一些不安全的事情,并且需要确认是否继续。
比如,在Elasticsearch7.0版本中,当一半以上的候选主节点永久丢失,集群将不会自动恢复。通常在有三个候选主节点的集群中,允许集群在不停机的情况下容忍其中一个节点丢失。如果其中两个永久丢失,则剩余的一个节点无法继续安全的工作。
7.0之前的版本允许集群悄然的从这种情况中恢复。用户可以通过启动全新的候选主节点来替换任意数量的丢失节点,从而使集群恢复工作。当候选主节点永久丢失一半以上时,集群自动恢复是不安全的,因为剩余的节点都不能确定是否有集群状态的最新版本,这很有可能会导致数据丢失。例如,部分节点丢失前,可能已经删除了某分片的副本,如果剩余的节点不知道这个状态,那么旧的分片副本可能会被提升为主分片。最危险的地方在于,用户完全没有意识到这一系列步骤使他们的集群处于数据丢失的危险之中,而意识到数据不一致时可能已经隔了数周或数月之久。
在Elasticsearch 7.0及以上的版本,与上面类似的不安全的操作将受到更多的限制,集群宁愿不可用等待人工修复而不是冒这种丢失数据的风险。如果您判断有绝对的必要,仍然可以执行这类不安全的操作,只需要采取一些额外步骤即可向集群确认您已经意识到风险并采取了很多措施。
如果您真的丢失了一半或以上的候选主节点,那么首先要尝试的是将丢失的候选主节点重新上线。如果节点的数据目录完好无损,那么最好的办法是使用这些数据启动新节点,通过采取这些措施,集群会同步最新的集群状态并安全的恢复工作。
接下来要尝试从最近的快照还原集群,但丢失了自拍摄快照以来所写的任何数据。然后,您可以再次重新索引任何丢失的数据,因为您知道缺少的数据的时间段。快照是增量的,因此您可以非常频繁地执行他们。每隔30分钟拍摄一次快照以限制此类恢复中丢失的数据量并不罕见。
如果上面这些恢复操作都不可行,最后的手段就是elasticsearch-node unsafe recovery tool
。这是一个命令行工具,系统管理员可以运行该工具来执行不安全的操作,例如从少数节点中集群状态版本比较旧的节点为主节点。通过把破坏集群一致性的操作都明显的暴露给用户,Elasticsearch 7.0消除了许多无意中导致数据丢失风险的情况。
运行
如果你熟悉分布式系统的理论,则可以将集群协调视为分布式共识协议的一种实现。在开发Elasticsearch早期版本的时候,分布式共识并未被广泛理解,然而近几个版本以来在这方面的技术有着显著的提高。
Zen Discovery模块从分布式共识算法中吸收了很多好的想法,但并不是完全严格遵循理论规定的模型。模块的实现具有非常保守的超时时间,使其有时在失败后恢复非常缓慢。在7.0中引入新的集群协调子系统使我们有机会更加密切地遵循理论模型。
分布式协调是一个公认的技术难点。我们在很大程度上依靠正式的方法来预先验证我们的设计,自动化工具在正确性和安全性方面提供了强有力的保证。您可以在我们的公共代码库中找到Elasticsearch新的集群协调算法的正式规范。该算法的核心安全模块非常简洁,并且算法模型与实现代码之间有一一对应的关系。
如果您了解过相关的分布式共识算法,如Paxos,Raft和Viewstamped Replication(VR),会发现Elasticsearch核心的模块看起来会很熟悉。他模拟一个可重写的寄存器,并使用一个master term
的概念,与Paxos的ballots,Raft的term,VR算法的view非常相似。分布式协调的核心安全模块及模型还覆盖了集群引导,跨节点重启的持久性以及动态重新配置。所有这些功能对于确保系统在所有情况下都能正常运行非常重要。
围绕这个健壮性理论的核心,我们构建了一个活跃层,以确保无论集群发生什么故障,一旦网络恢复且足够的节点恢复在线,集群将会立刻选出一个主节点并能够发布最新的集群状态更新。活跃层使用了许多最先进的技术来避免许多常见问题。选举调度程序能适配网络的条件来改变执行的行为来避免有争议的选举。学习了Raft风格的投票前阶段来抑制无法取胜的选举,避免了流氓节点的破坏。数据版本滞后检测可防止节点数据滞后主节点数据情况下破坏集群。主动双向故障检测可保证集群中的节点始终可以互相通信。尽量让大多数的集一个节点复制到另一个节点。集群状态更新为小差异的增量发布,避免将全量集群状态从一个节点复制到另一个节点。优雅的停止主节点将显示的放弃选择后继,避免全面选举来减少故障转移期间的停机时间。我们部署了测试的基础架构,可以有效地模拟可能持续数秒,数分钟或数小时的异常中断的情况,从而使我们能够验证在中断解决后集群始终能够快速恢复。
Raft协议
在社区中,我们常常会被问到一个问题,为什么Elasticsearch不简单的置入标准的分布式共识算法,如Raft协议。我们调研了很多知名的算法,每种都会根据场景在算法实现上做出不同的权衡考量。我们仔细评估各类算法并从所有能找到的文献中汲取灵感。我们早期的一个Poc使用了更接近Raft的协议,通过这次经验,我们了解到将其与Elasticsearch完全集成兼容所需要的工作量是巨大的。许多Raft中的标准算法还限定了一些对Elasticsearch来说不是最理想的设计决策。如:
- 它们通常围绕操作日志构建,而Elasticsearch的集群协调更直接地基于集群状态本身。这使得如批量这类重要的优化可以实现的更简单。
- 它们通常只有有限的扩容或缩容的能力,需要一系列的步骤来实现许多维护任务,而Elasticsearch的集群协调可以在一个步骤中安全地执行任意的变更。这是通过避免有问题的中间状态来简化系统间的实现。
- 他们通常非常注重安全性,但是并没有将如何包活的细节暴露出来,并且也没有描述清楚如果发现节点不健康应该如何处理。Elasticsearch的监控检查很复杂,已经在生产环境中使用和改进了很多年,对我们来说保持其现有行为非常重要。事实上,实现系统的安全特性所需要的工作要不保活及健康检查要少的多。大部分的工作主要都集中在系统的可用性检查上。
- 改进过程中有一个重要的目标是要支持从运行老的Zen Discovery的6.7集群不停机滚动升级至运行7.x的版本。将任何标准算法调整为允许这种滚动升级似乎都是行不通的。
要实现一个工业级的分布式共识算法,需要耗费大量的精力及人力来开发,并且实际情况已经超出了学术文献所概述的范围。在实践中不可避免的需要定制,但因为协议很复杂,任何定制化都有引入错误的风险。从工程角度来看,最有效的方法是吸纳一些好的思想,基于定制化的需求开发一套新的协议。
总结
Elasticsearch 7.0 新增了一个更快,更安全,更易于使用的新的集群分布式协调子系统。它支持从6.7开始不停机的滚动升级,并为弹性复制集提供基础。要试用新的集群协调子系统,请下载最新的7.0版本,查阅文档,了解它,并给我们一些反馈。