Elasticsearch 常见问题排查方法

译文 2025-10-21 10:40:27

Elasticsearch本身是一款复杂的软件,而当你启动多个实例以形成集群时,其复杂性会进一步增加。这种复杂性伴随着出现问题的风险。在本节课中,我们将探讨一些你在Elasticsearch使用过程中可能会遇到的常见问题。潜在的问题远不止我们在本节课中能涵盖的内容,因此我们将重点关注那些最常见的问题,主要与节点设置、集群组建和集群状态相关。

潜在的Elasticsearch问题可以根据以下Elasticsearch生命周期进行分类。

Elasticsearch 问题类型

节点启动

潜在问题包括安装和初始启动。这些问题会因集群的运行方式而有很大差异,例如是本地安装、在容器中运行还是云服务等。在本节课中,我们将按照本地安装的流程进行,并重点关注节点启动时非常重要的引导检查。

Discovery 和集群构建

这一类别涵盖与发现过程相关的问题,当节点需要相互通信以建立集群关系时会涉及这些问题。这可能包括集群初始引导期间的问题、节点无法加入集群以及主节点选举方面的问题。

数据索引和 Sharding

这包括与索引设置和映射相关的问题,但由于其他课程中已经涵盖了这些内容,我们将仅简要提及分片问题在集群状态中是如何体现的。

Search

搜索是设置过程的最终步骤,可能会引发与返回相关性较低结果的查询相关的问题,或者与搜索性能相关的问题。本课程的另一讲会涉及这个主题。

到此我们已经了解了一些潜在的Elasticsearch问题的初步背景,让我们采用实用的方法逐一进行分析。我们将揭示这些陷阱,并展示如何克服它们。

首先,ElasticSearch 备份

在我们开始破坏集群以模拟现实世界中的问题之前,让我们备份现有的索引。这将有两个好处:

  • 我们完成操作后,无法回到结束时的状态并继续进行
  • 我们将更好地理解在排查问题时进行备份以防止数据丢失的重要性

首先,我们需要设置我们的仓库。

打开主配置文件:

sudo vim /etc/elasticsearch/elasticsearch.yml

并确保你的机器上有一个已注册的存储库路径:

path.repo: ["/home/student/backups"]

接下来,确保该目录存在且Elasticsearch能够写入该目录:

mkdir -p /home/student/backups
chgrp elasticsearch /home/student/backups
chmod g+w /home/student/backups/

现在我们可以在以下路径将新的存储库注册到Elasticsearch:

curl --request PUT localhost:9200/_snapshot/backup-repo 
--header "Content-Type: application/json" 
--data-raw '{
"type": "fs",
"settings": {
      "location": "/home/student/backups/backup-repo"
  }
}'

最后,我们可以启动快照过程来进行备份。

curl --request PUT localhost:9200/_snapshot/backup-repo/snapshot-1

您可以通过一个简单的GET请求来检查该过程的状态:

curl --request GET localhost:9200/_snapshot/backup-repo/snapshot-1?pretty

我们应该会看到成功状态:

"state" : "SUCCESS"

非常好!既然我们已经备份了数据,现在就可以折腾我们的集群了。

分析 Elasticsearch 日志

好的,现在我们可以开始了。让我们回顾一下基础知识。我们将从查看Elasticsearch日志开始。

它们的位置取决于你在elasticsearch.yml中的path.logs设置。默认情况下,它们位于/var/log/elasticsearch/your-cluster-name.log

基本的 tail 命令可能会派上用场,用于实时监控日志:

tail -n 100 /var/log/elasticsearch/lecture-cluster.log
tail -n 500 /var/log/elasticsearch/lecture-cluster.log | grep ERROR

注意:有时,用上下文参数抓取几条周围的日志行也很有用,因为消息和堆栈跟踪可能是多行的:

cat lecture-cluster.log | grep Bootstrap --context=3

Log Permission Denied

但很快……我们就遇到了第一个问题!没有足够的权限实际读取日志:

tail: cannot open '/var/log/elasticsearch/lecture-cluster.log' for reading: Permission denied

解决这个问题有多种方法。例如,为你的Linux用户进行有效的组分配,或者一种通常更简单的方法是赋予用户以elasticsearch用户身份运行shell的sudo权限。

你可以通过编辑sudoers文件(以root用户身份使用visudo命令)并添加以下行来实现这一点:

username ALL=(elasticsearch) NOPASSWD:ALL

之后,你可以运行以下命令,以elasticsearch用户的身份启动一个新的shell:

sudo -su elasticsearch

检查 Bootstrap

引导检查是节点启动期间执行的预检验证,旨在确保节点能够正常履行其功能。有两种模式决定引导检查的执行方式:

  • 开发模式是指仅将节点绑定到环回地址(本地主机),或者显式设置discovery.type为single-node。在开发模式下不执行引导检查。
  • 生产模式是指将节点绑定到非环回地址(例如,0.0.0.0表示所有接口),从而使其可被其他节点访问。引导检查在该模式下执行。

让我们看看它们的实际运行情况,因为当检查不通过时,要弄清楚发生了什么可能会变成一项繁琐的工作。

禁用交换和内存锁定

Elastic推荐的首批系统设置之一是禁用堆交换。这是有道理的,因为Elasticsearch对内存的占用极大,你不会希望从磁盘加载“内存数据”。

根据文档,有两种选项:

  • 完全删除交换文件(或最小化swappiness)。这是首选选项,但需要以root用户身份进行相当大的干预
  • 或者需要在elasticsearch.yml中添加bootstrap.memory_lock参数。

让我们尝试第二种选项。打开你的主配置文件并插入这个参数:

vim /etc/elasticsearch/elasticsearch.yml
 
bootstrap.memory_lock: true

现在,启动Elasticsearch:

sudo systemctl start elasticsearch

等待节点启动片刻后,你会看到以下消息:

当你查看日志时,会发现“memory is not locked”

但我们之前不是刚锁定它了吗?其实并没有。我们只是请求了锁定,但它实际上并没有被锁定,所以我们触发了内存锁定的启动检查。

在我们的案例中,简单的方法是允许对我们的systemd单元文件进行锁定和覆盖,如下所示:

sudo systemctl edit elasticsearch.service

让我们添加以下配置参数:

[Service]
LimitMEMLOCK=infinity

现在当你启动时,应该就没问题了:

sudo systemctl start elasticsearch.service

设置Heap

如果你开始修改jvm.options文件中的JVM设置(你很可能需要这么做,因为默认情况下,这些设置对于实际的生产使用来说过低),你可能会遇到与上述类似的问题。这是怎么回事呢?原因是将初始堆大小设置得低于最大堆大小,这在Java领域其实是很常见的做法。

让我们打开配置文件,降低初始堆大小,看看会发生什么:

vim /etc/elasticsearch/jvm.options
# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space
 
-Xms500m
-Xmx1g

继续启动你的服务,你会发现另一条失败消息,因为我们遇到了堆大小检查。Elasticsearch日志证实了这一点:

一般来说,这个问题还与内存锁定有关,因为在程序运行过程中可能需要增大堆大小,而这可能会产生不良后果。

所以记住要将这些数字设置为:

  • 相等的值
  • 并且实际值要遵循Elastic的建议,简而言之,该值要低于32Gb,且最多为可用RAM内存的一半。

其他系统检查

运行时平台及其设置还有许多其他的启动检查,包括文件描述符检查、最大线程数检查、最大虚拟内存大小检查以及许多其他检查。你绝对应该在文档中浏览它们的描述。但由于我们运行的是官方的Debian发行版,它附带了一个预定义的systemd单元文件,因此其中大多数问题(以及其他一些问题)在该单元文件中都已为我们解决。

检查单元文件,查看已配置的各个参数:

cat /usr/lib/systemd/system/elasticsearch.service

请记住,如果你“自行”运行Elasticsearch二进制文件,你也需要处理这些问题。

Discovery 配置

有三个关键参数控制着集群的形成和发现过程:

  • discovery.seed_hosts 这理想情况下是我们想要加入的集群中所有符合主节点条件的节点的列表,我们将从这些节点获取最后的集群状态。
  • discovery.seed_providers 你也可以以文件的形式提供种子主机列表,该文件会在发生任何更改时重新加载。
  • cluster.initial_master_nodes 这是 node.names 列表(非 hostnames)。在所有这些节点加入(并投票)之前,集群设置不会完成。

但是,如果你不想形成任何集群,而是想在小型单节点设置中运行呢?你只需要在elasticsearch.yml中省略这些内容,对吗?

# --------------------------------- Discovery ----------------------------------
#discovery.seed_hosts: ["127.0.0.1"]
#cluster.initial_master_nodes: ["node-1"]

不行,那样行不通。启动后,你会遇到另一个引导错误,因为要通过这个引导检查,至少需要设置这些参数中的一个:

让我们来看看原因所在,并深入探讨发现过程的故障排除。

集群和 Discovery

在我们成功通过引导检查并首次启动节点后,其生命周期的下一阶段便是发现过程。

为了模拟一个全新集群的形成,我们需要一个“干净”的节点。我们需要删除该节点的所有数据,从而也会丢失任何先前的集群状态信息。

请记住,这实际上只是为了实验。在实际的生产环境中,几乎没有理由这样做:

sudo systemctl stop elasticsearch
rm -rf /var/lib/elasticsearch/*

加入一个已存在的集群

现在让我们设想一种情况:我们已经有了一个集群,只想让节点加入其中。因此,我们需要确保:

  • cluster.name 设置正确
  • discovery.seed_hosts 包含集群中现有主节点的地址
vim /etc/elasticsearch/elasticsearch.yml
# put in following parameter:
cluster.name: lecture-cluster
discovery.seed_hosts: ["10.1.2.3:9301"]

填写此参数也意味着符合前面描述的引导检查,因此节点启动应该不会有任何问题。

sudo systemctl start elasticsearch.service

为了确认我们的节点已成功运行,我们可以访问根端点(“/”):

curl localhost:9200/

实际上,我们得到了一个包含各种细节的良好响应:

但缺少了一些东西……cluster_uuid。这意味着我们的集群尚未形成。我们可以通过使用_cluster/health API检查集群状态来确认这一点:

curl localhost:9200/_cluster/health

等待30秒后,我们得到以下异常:

最后,让我们跟踪日志,看看该节点没有发现任何主节点,并且会继续发现过程:

形成新集群

在形成新集群时,所提到的这些问题可能会非常相似。我们可以在自己的环境中通过cluster.initial_master_nodes设置来模拟这种情况。再次确保你的节点上没有之前的数据。

vim /etc/elasticsearch/elasticsearch.yml
# put in following parameters:
cluster.name: lecture-cluster
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]

你可以先尝试之前的请求来启动。

在日志中,我们看到node-1正尝试发现第二个和第三个节点以进行选举来启动集群,但未成功:

总结

我们做了一些实验,所以你需要发挥想象力来补全整个情况。在真实的“生产”场景中,这个问题经常出现的原因有很多。由于我们处理的是分布式系统,许多外部因素(如网络通信)会产生影响,并可能导致节点之间无法相互连接。

为解决这些问题,你需要仔细检查以下内容:

  • cluster.name 所有节点都在加入或形成“正确的”集群
  • node.names 节点名称中的任何拼写错误都可能导致主节点选举无效
  • seed_hosts 确保你已链接有效的种子主机,且端口确实是已配置的那些端口
  • 网络连接性:使用telnet或类似工具检查你的网络,确保网络对节点之间的通信是开放的(特别是传输层和端口)
  • ssl/tls:通信加密是一个广泛的话题(我们在此不做深入探讨),并且它通常是问题的来源(无效证书、不受信任的证书颁发机构等)。此外,请注意,在加密节点到节点的通信时,对证书有特殊的要求。

分片和集群状态

我们接下来要探讨的最后一个内容是分片分配与集群状态之间的关系,因为这两者紧密相关。

但首先,我们需要修改elasticsearch.yml配置,让我们的节点成功形成一个单节点集群。打开主配置文件,将初始主节点设置为节点本身,然后启动服务:

vim /etc/elasticsearch/elasticsearch.yml
# --------------------------------- Discovery ----------------------------------
cluster.initial_master_nodes: ["node-1"]

然后我们请求 /_cluster/health

curl localhost:9200/_cluster/health

你应该会看到集群状态为绿色:

那么集群状态是什么意思呢?它实际上反映了我们集群中任何索引的最差状态。不同的选项包括:

  • red:索引的一个或多个分片未在集群中分配。这可能是由集群级别的各种问题引起的,例如节点断开连接或磁盘问题等。通常,红色状态标志着非常严重的问题,因此要做好可能出现数据丢失的准备。
  • yellow:主数据(目前)未受影响,所有主分片均正常,但一些副本分片未被分配,例如,按照设计,副本不会与主分片分配在同一个节点上。此状态表明存在数据丢失的风险。
  • green:所有分片都已妥善分配。但是,这并不意味着在单节点集群中数据得到了安全的复制,因为对于单分片索引来说,集群状态也会是绿色的。

Yellow 状态

那么现在让我们创建一个包含一个主分片和一个副本的索引:

curl --request PUT localhost:9200/test 
--header "Content-Type: application/json" 
--data-raw '{
 "settings": {
       "number_of_shards": 1,
       "number_of_replicas": 1
   }
}'

突然,我们的集群变成了黄色,因为我们最差的索引(也是我们仅有的一个索引)也变成了黄色。

你可以使用_cat/shards API检查分片分配情况,并会看到一个未分配的分片:

curl localhost:9200/_cat/shards?v

或者,如果你想获取更具描述性的信息,可以使用_cluster/allocation/explain API,它会解释各个分片未被分配的原因:

curl localhost:9200/_cluster/allocation/explain?pretty

正如前面所提到的,在我们的案例中,原因是不允许将数据副本分配到同一个节点,因为从弹性角度来看,这样做毫无意义。

那么如何解决这个问题呢?我们有两个选择。

  • 要么移除副本分片,这并非真正的解决方案,因为这会导致数据不再冗余,从而增加了数据丢失的风险。
  • 要么,添加另一个节点,以便重新分配分片。我们选择第二条路!

注意:我们在此处不会重复本地多节点集群的配置步骤,因此请回顾我们讲解这些步骤的课程。通常,我们需要一个带有单独配置的独立 systemd 单元文件。

我们可以检查第二个节点的主配置文件,以确保它能加入与现有节点相同的集群。环回地址将被用作种子主机:

vim /etc/elasticsearch-node-2/elasticsearch.yml
 
# make sure it also consist of following two params
cluster.name: lecture-cluster
# --------------------------------- Discovery ----------------------------------
discovery.seed_hosts: ["127.0.0.1"]

启动第二个节点后,我们可以再次检查集群状态:

systemctl start elasticsearch-node-2.service
curl --silent localhost:9200/_cluster/health?pretty

这次我们得到了绿色状态,因为副本分片现在已成功分配到第二个节点。

Red 状态

让我们继续用这个例子来模拟红色集群状态。首先删除索引并重新创建它,但这次只设置2个主分片,不设置副本分片。你很快就会明白这为什么是个糟糕的主意:

curl --request DELETE localhost:9200/test
curl --request PUT localhost:9200/test 
--header "Content-Type: application/json" 
--data-raw '{
 "settings": {
       "number_of_shards": 2,
       "number_of_replicas": 0
   }
}'

现在让我们检查分片分配:

curl localhost:9200/_cat/shards?v

我们可以看到每个主分片都位于不同的节点上,这遵循了在集群级别和索引级别设置的标准分配规则。

你可能知道我们接下来要讲什么。想象一下这种情况:出现一些网络问题,导致你的集群“分裂”,从而使节点通信中断。或者更糟的是,当一些磁盘出现故障时,会导致节点无法正常运行。

我们可以模拟这种情况的简单方法是停止我们的一个节点:

sudo systemctl stop elasticsearch-node-2.service

我们的集群立刻变成了可能出现的最糟糕的颜色:

"status" : "red"

如果你现在查看 explain API:

curl localhost:9200/_cluster/allocation/explain?pretty

您会得到详细描述的原因:

  • 一个节点因我们将其关闭而离开,但在现实世界中,这可能有多种潜在原因
  • 在集群中找不到有效的分片副本,在这种情况下,我们的数据丢失了

不幸的是,这种情况没有简单的解决办法,因为我们没有任何副本,也无法“重新创建”我们的数据。

首先,如果你正在处理一些网络问题,试着彻底检查可能出现问题的地方,比如防火墙配置错误等,并优先进行检查,因为在这种状态下数据无法被稳定地索引。

根据文档路由的不同,许多索引请求可能会指向缺失的分片,最终导致超时:

curl --request POST localhost:9200/test/_doc 
--header "Content-Type: application/json" 
--data-raw '{ "message": "data" }'

会有如下异常:

其次,如果找不到任何可行的解决方案,要让索引正常工作,剩下的唯一选择可能就是分配一个新的分片。但要注意,即使丢失的节点之后重新上线,新分片也会覆盖它,因为新分片处于更加新的状态。

你可以使用_cluster/reroute API分配一个新的分片。在这里,我们为运行正常的node-1上的test索引分配一个分片。注意,你必须明确接受数据丢失:

curl --request POST "localhost:9200/_cluster/reroute?pretty" 
--header "Content-Type: application/json" 
--data-raw '{
   "commands" : [
       {
         "allocate_empty_primary" : {
               "index" : "test",
               "shard" : 1,
               "node" : "node-1",
               "accept_data_loss" : "true"
         }
       }
   ]
}'

之后,你在索引过程中应该不会再遇到超时问题了。

从备份恢复

为确保不会留下我们引入的遗留问题,我们将恢复之前备份的所有原始索引。但在这之前,我们需要进行一些清理工作。

首先,我们需要确保仓库路径在elasticsearch.yml中重新注册,因为我们在练习过程中对其进行了一些更改。请参考你在课程开始时创建并保存的配置文件。

sudo vim /etc/elasticsearch/elasticsearch.yml
path.repo: ["/home/student/backups"]

完成之后,我们可以重启主节点:

sudo systemctl restart elasticsearch.service

然后我们可以重新注册我们的仓库,以确保它已准备好提供备份数据:

curl --request PUT localhost:9200/_snapshot/backup-repo 
--header "Content-Type: application/json" 
--data-raw '{
"type": "fs",
"settings": {
      "location": "/home/student/backups/backup-repo"
  }
}'

你可以通过向我们的备份仓库发送一个简单的_cat请求来查看仓库中可用的快照,我们应该能看到等待恢复的 snapshot-1

curl localhost:9200/_cat/snapshots/backup-repo

现在,为了防止在恢复过程中进行任何写入操作,我们需要确保所有索引都已关闭:

curl --request POST localhost:9200/_all/_close

最后我们可以恢复备份了:

curl --request POST localhost:9200/_snapshot/backup-repo/snapshot-1/_restore

几秒钟后,如果你检查你的索引,应该会看到所有原始数据都已恢复原位:

curl localhost:9200/_cat/indices

太棒了!既然你已经掌握了排查Elasticsearch集群问题的基础知识和各种命令,最后一条建议就是要保持积极的心态,即使遇到问题无法解决时也是如此。这是作为一名Elasticsearch工程师必不可少的一部分。

原文:https://coralogix.com/blog/troubleshooting-common-elasticsearch-problems/

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat