[Devops Fire] Elasticsearch 避坑指南及面试常见问题

2020-05-22 00:00:00 索引 数据 集群 节点 分片

引言

本文主要讲述在生产环境中使用 Elasticserch 遇到的一些坑,以及如何避开这些坑。对于正在使用或即将使用 Elasticsearch 的用户来说可能可做参考,以下内容都是基于我个人的实践经验总结,如有任何错误或不足之处,请大家指正。

本文不做任何 Elasticsearch 概念和基础普及,需要读者对 Elasticsearch 的概念和名词有基础的认识,每个问题都会有佳实践板块,方便快速查看。大家也可先收藏,等遇到问题后再来查看。

常见坑

  • Java 相关
  • 分布式相关
  • 集群健康度
  • 节点诊断
  • Shard 相关
  • 深度分页

Java 相关

由于 Elasticsearch 是 Java 开发,Java 相关的问题如 JVM 等都是不可避免的,这里先说说所有人肯定会遇到的,就是 Java 的虚拟机内存问题。

Elasticsearch 使用 ES_JAVA_OPTS 环境变量来配置 JVM。其中比较常用的配置是:

  • - Xms:小堆内存
  • - Xmx:大堆内存

这两个值建议设置成一样的,不超过物理机或虚拟机的 50 %,且不超过 32G。设置一样的是减轻堆伸缩带来堆压力。不超过 50 % 是因为必须要预留足够的内存给操作系统及其他进程,同时 JVM 本身和 Elasticsearch 的部分功能也需要。不超过 32G 是因为超过 32G JVM 会启用压缩普通对象指针(compressed object pointers)(compressed oops),部分操作系统可能在 26G 以上就会开启零基压缩(zero-based compressed oops)。

佳实践

Elasticsearch 启动环境变量设置:ES_JAVA_OPTS="-Xms2g -Xmx2g",替换 2g 为使用的物理机或虚拟机内存的一半(低 1G,多 32G)。


分布式相关

分布式大的问题就是脑裂,脑裂问题简单来说就是没人做主。比如我们多个人一起讨论问题可以使用少数服从多数来表决,但是如果是两个人,那互不相让就没法决定了。分布式系统也是这样,当集群有两个节点时,如果因为网络问题导致两个节点失联,那么它们都会认为是对方的问题(挂了),认为自己没问题,自己应该变成主节点,所以集群就变成两个主节点。

如果这时候网络恢复正常,而它们的数据不一致,就会变成互不相让的局面。

而如果我们的集群有三个节点(A,B,C),如果 A 和 B,C 失联,那么 B 和 C 会发现它们都连不上 A,就会标记 A 为失联,而它们可以选举新的主节点。所以分布式系统一般使用 2n + 1 (n > 0)个节点,生产系统中少为 3 个节点。

佳实践

分布式部署节点数量为奇数(3,5,7 ...),且在不同的网络拓扑上(例如公有云的不同区域或可用区,机房的不同机架),同时配置弹性伸缩,在节点出现问题时自动调节节点数量。这里的节点仅指可成为主节点的节点(配置 master: true),其他节点不影响。


集群健康度

Elasticsearch 的集群健康度是一个非常重要的监控指标,是 Elasticsearch 暴露的一个整体指标,如果你只能监控一个指标的话,那就是它了。健康度按层级分为分片健康度、索引健康度和集群健康度,指标分为绿、黄、红三个等级。

分片健康度

  • 红(red):至少一个主分片没有被分配
  • 黄(yellow):至少一个副本没有被分配
  • 绿(green):主副分片都正常分配

索引健康度

索引健康度就是此索引所有分片中差的健康度,即只要有分片是红,索引就是红,只有分片都是绿,索引才是绿。

集群健康度

集群健康度是此集群上所有索引中差度健康度,即只要有索引是红,集群就是红,只有索引都是绿,集群才是绿。

健康度相关 API

Elasticserch 提供了一系列的 API 供我们获取健康度。

GET /_cluster/health 获取集群的健康状态【文档】

GET /_cluster/health?level=indices 获取所有索引的健康状态

GET /_cluster/health/<index> 获取单个索引的健康状态

GET /_cluster/allocation/explain 返回个未分配分片的原因【文档】

集群不健康排查流程

Elasticsearch 集群出现红或者黄等不健康状态是很常见等问题,当集群出现不健康状态时(红或者黄),我们需要一步步找到问题的原因再修复。

首先,定位问题,可以通过上述的 API 查看不健康状态的原因。 比较常见的问题原因主要有:

  • 节点离线导致的分片无法分配
  • 索引配置、分片规则等问题导致的分片无法分配
  • 磁盘空间不足

在定位问题原因之后,我们就可以根据情况确定解决方案。在官网的这个页面(elastic.co/guide/en/ela)可以看到 Elasticsearch 分片无法分配的错误,下面我们来讲讲一些错误的解决办法。

  • 节点离线后,由于主副分片不能位于同一节点上,可能导致分片分配失败,解决办法就是增加节点,一般对于生产集群,我们都会根据情况保留一定的节点数量,在节点离线时及时启动新的节点加入集群。
  • 对于索引配置,分配规则等问题导致的无法分配,如没有满足规则的节点,只有一个测试节点,但是默认配置来一个主副分片等。我们就要根据实际情况来修改配置或规则,对于测试环境,删除重建是快的方法,而对于生产环境,就需要结合实际情况来考虑了。
  • 对于磁盘空间不足的问题,需要扩充磁盘或者将索引迁移到其他节点上。
  • DANGLING_INDEX_IMPORTED:当集群中的某个节点离线后进行了索引的删除,然后这个节点又回到集群,就会发生这个错误,主要是索引的数据不一致导致的,可以重新在删除已被删除的索引即可。
  • EXISTING_INDEX_RESTORED:当集群中的某个索引被关闭(关闭不等于删除),又恢复到这个索引就会引发这个问题,需要把索引删除后再恢复。

同时,需要注意的是,在分片重新分配的过程中,会出现短暂的不健康状态,在监控时需要设置合理的告警阈值,避免过于敏感。


节点诊断

节点是从集群的物理机或者虚拟机层面来看,虽然 Elasticsearch 认为节点都是不可靠的,当节点出现问题后,我们会用一些方法保证集群的可靠。但是我们不可能在节点出现问题后,只是简单粗暴地更换节点,还是需要一些方法来确保节点基本没有问题。

节点诊断相关 API

GET /_cat/nodes?v 查看节点到基本信息即负载情况【文档】

GET /_nodes/stats/indices 查看节点的索引详情【文档】

POST /_cache/clear 清除节点缓存【文档】

节点内存问题

Elasticsearch 集群节点有可能发生的就是内存问题,Java 的长时间 GC 等可能导致的集群变慢,产生 OOM(Out Of Memory),甚至是节点离线。下面我们来讲讲这些问题的产生原因及解决办法。

首先,我们也需要通过上述的 API 及节点的监控找到问题的原因,比较常见的内存问题主要有:

缓存占用过多内存,如 Segment 占用过多内存可能是由于频繁写入导致产生了很多零散的 Segment,可以使用 Force Merge API 将它们合并为一个。如果使用了 FieldData 且没有限制 FieldData 缓存的上限,可能因为消耗多大导致频繁 GC。FieldData 是针对 Text 类型做排序和聚合使用的(像 Keyword 一样),一般不推荐使用(elastic.co/guide/en/ela)。

大量复杂的嵌套聚合也可能引发频繁 GC,因为嵌套聚合会在内存中生成大量的 Bucket 对象,生产环境中应该尽可能避免复杂的嵌套聚合查询。

如果无法完全限制请求的查询,Elasticsearch 提供了断路器的功能,用户可以根据需求配置相应规则,只要满足规则,请求就会熔断,从而保护集群,避免生产 OOM 问题。配置也很简单,这里就不展开,详细可以查看官方文档(elastic.co/guide/en/ela)。

佳实践

对于频繁写入的索引,需要定期监控节点内存,必要时使用 Force Merge。 对于复杂查询与聚合,要在业务端控制查询请求,如无法限制,则可使用断路器。断路器只是后对集群的保护,如果用户的查询请求总是消耗过多的资源而被断路器中断,用户可能频繁尝试且获取不到结果,对用户体验不好,后台压力也不小,还是需要业务端及时调整和控制。


Shard 相关

Elasticsearch 把一个索引切分成多个 Shard 来存储,将一份大的数据切分成多个部分来存储可以提高查询效率,也便于将数据分布式存储。不过每个索引的 Shard 数量是固定的,必须在创建时指定,后期修改必须要重建索引。分片的数量和大小都需要根据实际情况调整,一个分片实际上是一个 Lucene 索引,过多的分片会导致额外的性能开销,同时过多的分片也会导致聚合不准的问题(elastic.co/guide/en/ela)。一般来说,单个 Shard 的建议大大小是 20G 到 50G,对于普通搜索类数据,好控制在 20G,而对于时间序列类数据(如日志)好控制在 50G。如果每天的日志量无法预估,可以使用 Rollover API 进行自动的索引生命周期管理(elastic.co/guide/en/ela)。而单个节点的总数据量好在 2 TB 以内。

因为 Shard 直接关乎集群健康度,所以对于生产集群来说,这个设置尤为重要,我们也不希望因为设置的问题导致集群变黄或红。

Shard 操作相关 API

GET /_cat/shards/<index> 查看 Shard 信息【文档】

POST /<alias>/_rollover/<target-index> 当索引满足某些条件时(如数据量太大)自动切到新的索引,非常适合无法预估大小的时间序列类索引【文档】

POST /<index>/_forcemerge 强制合并索引数据【文档】

POST /<index>/_shrink/<target-index> 新建索引并减少主分片数量【文档】

POST /<index>/_split/<target-index> 新建索引并增加主分片数量【文档】

POST /_reindex 重建索引【文档】

这些 API 的使用需要有很多限制条件,具体大家可以查看各个 API 的文档,大家可以提前了解下避免出现使用时用不了的尴尬。

佳实践

分片的数量和大小都需要根据实际情况调整,一般来说,单个 Shard 的建议大大小是 20G 到 50G,对于普通搜索类数据,好控制在 20G,而对于时间序列类数据(如日志)好控制在 50G。如果每天的日志量无法预估,可以使用 Rollover API 进行自动的索引生命周期管理。而单个节点的总数据量好在 2 TB 以内。


深度分页

Elasticsearch 有三种分页方式。

from + size 参数

from 定义数据偏移量,size 定义获取数据量,from 和 size 的方式类似 SQL 的 offset 和 limit,很好理解,但是在分页量大时(深度分页)效率很差。因为这会从每个分片获取适量的数据,在查询节点上缓存数据。例如 from=1000,size=10,每个分片会排序上报 1001 * 10 条数据,查询节点会归并排序所有结果,并返回第 1001 到 1010 条数据。由于这个原因,当取的页码很大时,缓存节点的效率会很差,所以 Elasticsearch 会限制大的排序数据(配置 index.max_result_window,默认是 10000)。这种方式适合测试与小范围的分页。

from & size 分页www.elastic.co

相关文章