ElasticSearch合理分配索引分片原理
作者:cool小伙
Elasticsearch 是一个非常通用的平台,支持各种用户实例,并为组织数据和复制策略提供了极大的灵活性。但是,这种灵活性有时会使我们很难在早期确定如何很好地将数据组织成索引和分片,尤其是不熟悉 Elastic Stack。虽然不一定会在首次启动时引起问题,但随着数据量的增长,它们可能会导致性能问题。群集拥有的数据越多,纠正问题也越困难,因为有时可能需要重新索引大量数据。
因此,当我们遇到性能问题时,往往可以追溯到索引方式以及集群中分片的数量。那么就会遇到问题,我们应该有多少分片以及我的分片应该有多大。
一、什么是分片?
假如我们的集群的架构如下图:
集群(cluster): 由一个或多个节点组成, 并通过集群名称与其他集群进行区分
节点(node): 单个 ElasticSearch 实例. 通常一个节点运行在一个隔离的容器或虚拟机中
索引(index): 在 ES 中, 索引是一组文档的集合
分片(shard): 因为 ES 是个分布式的搜索引擎, 所以索引通常都会分解成不同部分, 而这些分布在不同节点的数据就是分片. ES自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节.
副本(replica): ES 默认为一个索引创建 5 个主分片, 并分别为其创建一个副本分片. 也就是说每个索引都由 5 个主分片成本, 而每个主分片都相应的有一个 copy。对于分布式搜索引擎来说, 分片及副本的分配将是高可用及快速搜索响应的设计核心.主分片与副本都能处理查询请求,它们的唯一区别在于只有主分片才能处理索引请求.副本对搜索性能非常重要,同时用户也可在任何时候添加或删除副本。额外的副本能给带来更大的容量, 更高的呑吐能力及更强的故障恢复能力。
如上图,有集群两个节点,并使用了默认的分片配置. ES自动把这5个主分片分配到2个节点上, 而它们分别对应的副本则在完全不同的节点上。其中 node1 有某个索引的分片1、2、3和副本分片4、5,node2 有该索引的分片4、5和副本分片1、2、3。
当数据被写入分片时,它会定期发布到磁盘上的不可变的 Lucene 分段中用于查询。随着分段数量的增长,这些分段会定期合并为更大的分段。 此过程称为合并。 由于所有分段都是不可变的,这意味着所使用的磁盘空间通常会在索引期间波动,因为需要在删除替换分段之前创建新的合并分段。 合并可能非常耗费资源,特别是在磁盘I / O方面。
分片是 Elasticsearch 集群分发数据的单元。 Elasticsearch 在重新平衡数据时可以移动分片的速度,例如发生故障后,将取决于分片的大小和数量以及网络和磁盘性能。
注1:避免使用非常大的分片,因为这会对群集从故障中恢复的能力产生负面影响。 对分片的大小没有固定的限制,但是通常情况下很多场景限制在 50GB 的分片大小以内。
注2:当在ElasticSearch集群中配置好你的索引后, 你要明白在集群运行中你无法调整分片设置. 既便以后你发现需要调整分片数量, 你也只能新建创建并对数据进行重新索引(reindex)(虽然reindex会比较耗时, 但至少能保证你不会停机).
主分片的配置与硬盘分区很类似, 在对一块空的硬盘空间进行分区时, 会要求用户先进行数据备份, 然后配置新的分区, 最后把数据写到新的分区上。
注3:尽可能使用基于时间的索引来管理数据保留期。 根据保留期将数据分组到索引中。 基于时间的索引还可以轻松地随时间改变主分片和副本的数量,因为可以更改下一个要生成的索引。
二、索引和分片是否是空闲的
对于每个 Elasticsearch 索引,有关映射和状态的信息都存储在集群状态中。它保存在内存中以便快速访问。 因此,在群集中具有大量索引可能导致较大的群集状态,尤其是在映射较大的情况下。 这可能会变得很慢,因为所有更新都需要通过单个线程完成,以便在更改集群中分布之前保证一致性。
每个分片都有需要保存在内存中的数据并使用堆空间。 这包括在分片级别保存信息的数据结构,但也包括在分段级别的数据结构,以便定义数据驻留在磁盘上的位置。 这些数据结构的大小不固定,并且会根据使用场景不同而有所不同。然而,分段相关开销的一个重要特征是它与分段的大小不严格成比例。 这意味着与较小的分段相比,较大的分段每个数据量的开销较小。 差异可能很大。为了能够为每个节点存储尽可能多的数据,管理堆的使用并尽可能减少开销变得很重要。 节点拥有的堆空间越多,它可以处理的数据和分片就越多。
因此,索引和分片在集群视角下不是空闲的,因为每个索引和分片都存在一定程度的资源开销。
分配的每个分片都是有额外的成本的:
每个分片本质上就是一个Lucene索引, 因此会消耗相应的文件句柄, 内存和CPU资源
每个搜索请求会调度到索引的每个分片中. 如果分片分散在不同的节点倒是问题不太. 但当分片开始竞争相同的硬件资源时, 性能便会逐步下降
ES 使用词频统计来计算相关性. 当然这些统计也会分配到各个分片上。如果在大量分片上只维护了很少的数据, 则将导致最终的文档相关性较差。
注1:小的分片会造成小的分段,从而会增加开销。我们的目的是将平均分片大小控制在几 GB 到几十 GB 之间。对于基于时间的数据的使用场景来说,通常将分片大小控制在 20GB 到 40GB 之间。
注2:由于每个分片的开销取决于分段的数量和大小,因此通过 forcemerge 操作强制将较小的分段合并为较大的分段,这样可以减少开销并提高查询性能。 理想情况下,一旦不再向索引写入数据,就应该这样做。 请注意,这是一项比较耗费性能和开销的操作,因此应该在非高峰时段执行。
注3:我们可以在节点上保留的分片数量与可用的堆内存成正比,但 Elasticsearch 没有强制的固定限制。 一个好的经验法则是确保每个节点的分片数量低于每GB堆内存配置20到25个分片。 因此,具有30GB堆内存的节点应该具有最多600-750个分片,但是低于该限制可以使其保持更好。 这通常有助于集群保持健康。
注4:如果担心数据的快速增长, 建议根据这条限制: ElasticSearch推荐的最大JVM堆空间 是 30~32G, 所以把分片最大容量限制为 30GB, 然后再对分片数量做合理估算。例如, 如果的数据能达到 200GB, 则最多分配7到8个分片。
注5:如果是基于日期的索引需求, 并且对索引数据的搜索场景非常少. 也许这些索引量将达到成百上千, 但每个索引的数据量只有1GB甚至更小. 对于这种类似场景, 建议是只需要为索引分配1个分片。如果使用ES的默认配置(5个分片), 并且使用 Logstash 按天生成索引, 那么 6 个月下来, 拥有的分片数将达到 890 个. 再多的话, 你的集群将难以工作--除非提供了更多(例如15个或更多)的节点。想一下, 大部分的 Logstash 用户并不会频繁的进行搜索, 甚至每分钟都不会有一次查询. 所以这种场景, 推荐更为经济使用的设置. 在这种场景下, 搜索性能并不是第一要素, 所以并不需要很多副本。 维护单个副本用于数据冗余已经足够。不过数据被不断载入到内存的比例相应也会变高。如果索引只需要一个分片, 那么使用 Logstash 的配置可以在 3 节点的集群中维持运行 6 个月。当然你至少需要使用 4GB 的内存, 不过建议使用 8GB, 因为在多数据云平台中使用 8GB 内存会有明显的网速以及更少的资源共享.
三、分片大小如何影响性能
在Elasticsearch中,每个查询在每个分片的单个线程中执行。 但是,可以并行处理多个分片,对同一分片也可以进行多个查询和聚合。
这意味着,如果不涉及缓存,则最小查询延迟将取决于数据、查询类型以及分片的大小。 查询大量小的分片将使每个分片的处理速度更快,但是需要按顺序排队和处理更多的任务,它不一定比查询较少数量的较大分片更快。 如果存在多个并发查询,则拥有大量小分片也会降低查询吞吐量。
从查询性能角度确定最大分片大小的最佳方法是使用实际数据和查询进行基准测试。 始终以查询和加载索引的节点在生产中需要处理的内容基准,因为优化单个查询可能会产生误导性结果。
四、如何管理分片大小
当使用基于时间的索引时,通常每个索引都与固定的时间段相关联。 每天的索引非常常见,通常用于保存保留期短的或每日量大的数据。 这些允许以合适的粒度管理保留期,并且可以轻松调整日常基础量。 具有较长保留期的数据,特别是如果每日的量不能保证使用每天的索引,通常使用每周或每月的索引以保证分片大小。 这减少了随着时间的推移需要存储在集群中的索引和分片的数量。
注:如果使用基于时间的索引,这个时间是某个固定的时间段,那么需要根据数据的保留期限和预期的数据量来调整每个索引所覆盖的时间段,以达到目标分片的大小。也就是说,如果我们要确定最终分片的大小,则需要根据我们的数据保存的期限以及预估预期的数据量来调整我们索引需要按照天还是周还是月的时间来进行评估。
当数据量可以合理预测并且变化缓慢时,具有固定时间间隔的基于时间的索引很有效。 如果索引快速变化,则很难保持统一的目标分片大小。为了能够更好地处理这种类型的场景,引入了 Rollover and Shrink API 这些为索引和分片的管理方式增加了很多灵活性,特别是对于基于时间的索引。
Rollover and Shrink API 可以指定应包含的文档和索引的数量和/或应该向其写入最大期限的文档。 一旦超出其中一个标准,Elasticsearch 就可以触发创建新索引,无需停机即可完成写入。 可以切换到特定大小的新索引,而不是让每个索引覆盖特定的时间段,这使得可以更容易地为所有索引实现均匀的分片大小。如果需要更新数据,在使用此API时,事件的时间戳与其所处的索引之间不再存在明显的链接,这可能会使更新效率大大降低,因为每次更新都需要在搜索之前进行。
注:如果我们有基于时间的不可变数据,其中数据量可能会随时间发生显著变化,就可以考虑使用 Rollover API,通过动态更改每个索引所涵盖的时间段来实现最佳目标分片大小。 这提供了极大的灵活性,并且可以帮助避免在数据量不可预测时具有太大或太小的分片。
Shrink API 允许我们将现有索引缩小为具有较少主分片的新索引。 如果在索引期间需要跨节点均匀扩展分片,但这会导致分片太小,一旦索引不再被索引,此 API 可用于减少主分片的数量。 这将生成更大的分片,更适合长期存储数据。
如果需要让每个索引覆盖特定的时间段,并且希望能够在大量节点上扩展索引,请考虑使用 Shrink API 在索引不再编入索引时减少主分片的数量。 如果最初配置了太多分片,此 API 还可用于减少分片数量。
五、总结
关于如何在索引和分片之间最佳地分布数据,这将取决于所使用的场景的细节,有时很难确定如何最好地应用可用的建议。
数据分片也是要有相应资源消耗,并且需要持续投入。当索引拥有较多分片时, 为了组装查询结果, ES 必须单独查询每个分片(当然并行的方式)并对结果进行合并。所以高性能 IO 设备(SSDs)和多核处理器无疑对分片性能会有巨大帮助。尽管如此, 还是要多关心数据本身的大小,更新频率以及未来的状态。在分片分配上并没有绝对的答案。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。