解决ES未分配分片的相关问题,“X 个分片有 Y 个失败”

Posted by Geuni's Blog on July 10, 2023

我们打开kibana的时候,有时候能看到“X 个分片有 Y 个失败”的错误信息,这通常是个别索引存在未分配分片所导致的问题。 这时候我们的ES节点状态通常是Yellow或是Red状态。

先了解一下ES Shard的4种状态:

  • INITIALIZING - 分片初始化状态,该状态下shard不可用。新建索引或刚启动节点的时候短暂的发生。

  • RELOCATING - 新增节点或节点掉线的时候会重新分配shard导致shard的移动,也通常是短暂的。

  • STARTED - shard活跃状态,该状态下可接收请求

  • UNASSIGNED - shard分配失败

那什么情况下shard会分配失败?

分片无法分配通常有如下几个原因:

  • 副本分片数设置过大,节点不够用于分配
  • 节点掉线时的延时机制
  • 分片数据丢失
  • 节点磁盘空间不够用
  • 集群中存在多个ES版本

首先查看集群状态 & 确认分片失败原因

首先通过Cluster health API,查看集群状态以及整体的分片的分配情况。

1
GET /_cluster/health\?pretty

返回如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "cluster_name" : "my-application",
  "status" : "yellow",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 565,
  "active_shards" : 565,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 60,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 90.4
}

重点关注status,unassigned_shards,active_shards_percent_as_number, delayed_unassigned_shards等几个数据。

status为集群状态,有三个状态

  • green - 所有分片已分配
  • yellow - 所有主分片已分配,部分副本分片尚未分配,这时候如果某个节点发生故障部分数据将不可用
  • red - 部分主分片尚未分配,部分数据将不可用,集群刚启动后分配主分片的时候也可能短暂发生

unassigned_shards是尚未分配的分片数量,当看到分片失败提示的时候,这里是会有数据的。

active_shards_percent_as_number是已分配分片的百分比,数值越小说明未分配的分片数量越多。

delayed_unassigned_shards是节点掉线后,等待延时时间(index.unassigned.node_left.delayed_timeout,默认1分钟)的待分配的shard数

然后通过cat shards API查看所有分片的分配情况

1
GET _cat/shards?h=index,shard,prirep,state,unassigned.reason

或通过shell只查看未分配的分片

1
curl -X GET localhost:9200/_cat/shards?h=index,shard,prirep,state,unassigned.reason| grep UNASSIGNED

查看分片未分配的具体原因:

1
GET /_cluster/allocation/explain?pretty

会有如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  "index" : "my-index",
  "shard" : 2,
  "primary" : false,
  "current_state" : "unassigned",
  "unassigned_info" : {
    "reason" : "CLUSTER_RECOVERED",
    "at" : "2023-02-06T06:34:22.345Z",
    "last_allocation_status" : "no_attempt"
  },
  "can_allocate" : "no",
  "allocate_explanation" : "cannot allocate because allocation is not permitted to any of the nodes",
  "node_allocation_decisions" : [
    {
      "node_id" : "dntxO1EFQVSzk7A4n45OLQ",
      "node_name" : "node-1",
      "transport_address" : "52.208.205.70:9300",
      "node_attributes" : {
        "ml.machine_memory" : "33737449472",
        "xpack.installed" : "true",
        "ml.max_open_jobs" : "20",
        "ml.enabled" : "true"
      },
      "node_decision" : "no",
      "deciders" : [
        {
          "decider" : "same_shard",
          "decision" : "NO",
          "explanation" : "the shard cannot be allocated to the same node on which a copy of the shard already exists [[my-index][2], node[dntxO1EFQVSzk7A4n45OLQ], [P], s[STARTED], a[id=ag-Komw1QJu9EKYkrsrdmw]]"
        }
      ]
    }
  ]
}

知道怎么确认原因了,下面看看各分片分配失败的情况及通常的解决方法。

如何解决?

副本分片数设置过大,节点不够用于分配

shard分为两种,一种是primary shard(主分片),而另一种是replica shard(副本分片)。每个索引文档都将归属于某个primary shard,primary shard数量由number_of_shards属性来决定。这些primary shard一般会有各自的副本,具体的副本数量是由number_of_replicas这个属性来指定的。我们通常是通过index template(索引模板)或创建索引的时候指定这些属性。

而当分配replica shard的时候,有一个重要的规则就是主节点不会将replica shard分配到其primary shard所在的节点上。虽说replica shard会提升查询性能,它存在的主要目的是创建冗余数据来实现高的可用。意思是当你的副本数大于等于节点数的时候,你的replica shard没地方可去。虽然对服务没有影响,但ES判断这种状态是不健康的。毕竟我们预期的number_of_replicas没能分配上。最简单又比较常见的case就是单节点而设置number_of_replicas为1(默认值)。

上一步执行的/_cluster/allocation/explain结果就是这个场景,能看到如下错误:

1
the shard cannot be allocated to the same node on which a copy of the shard already exists

这时候解决方法是增加节点数或减少副本数。如下:

1
2
3
4
5
6
PUT /my-index-000001/_settings
{
  "index" : {
    "number_of_replicas" : 0
  }
}

节点掉线时的延时机制

当节点掉线时会发生大规模的分片移动,这时候性能开销有点大成本有点高。集群环境中通常会发生短暂的网络故障,延迟什么的。如果每次发生网络延迟的时候都全部重新分配分片未免大动干戈了。为了解决这个问题ES有节点掉线时的分片延时分配机制。延时时间由index.unassigned.node_left.delayed_timeout属性(默认1分钟)控制

如果我们确定某个节点要永久剔除,可以将index.unassigned.node_left.delayed_timeout属性设置为0,让ES立即执行重新分配:

1
2
3
4
5
6
PUT _all/_settings
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "0"
  }
}

可根据实际需求增减该值。

分片数据丢失

假如我们有一个节点有尚未分配的primary shard,在没有任何副本的情况下节点宕机。这时候集群在全局的cluster state文件中检测到primary shard而在所有节点中没有找到该数据。这时候执行Cluster allocation explain API能看到如下错误:

1
"allocate_explanation" : "cannot allocate because a previous copy of the primary shard existed but can no longer be found on the nodes in the cluster",

这时候我们需要做个决策,让问题节点重新加入到集群,或通过 Cluster Reroute API 强制分配空的primary shard(会发生数据丢失)。

如果可以接受丢失该shard数据,决定强制重新分配可执行如下API:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /_cluster/reroute?pretty
{
    "commands" : [
        {
          "allocate_empty_primary" : {
                "index" : "<INDEX_NAME>",
                "shard" : 0,
                "node" : "<NODE_NAME>",
                "accept_data_loss" : "true"
          }
        }
    ]
}

当使用allocate_empty_primary命令的时候,因为比较危险会强制要求你指定accept_data_loss为true。以确认你已经准备好丢失数据了。

节点磁盘空间不够用

当没有足够的节点和磁盘空间的时候,主节点无法分配分片。默认当磁盘空间使用率到底85%的时候,节点将被标记为Low disk watermark,不会被分配更多的分片。

可通过cat allocation API确认各节点的分片分配情况及磁盘空间使用率。

1
GET /_cat/allocation?v

可以通过删除没用的数据,增加节点或增加服务器的磁盘可用空间等方式解决这个问题。

当我们的磁盘空间足够大的时候,默认的Low disk watermark可能就太低了。这时候可以通过Cluster update settings API进行修改。

例如调整为90%,需要注意的是这是安全点位,我们应该根据实际的数据增长速率来设置一个合适的值。

1
2
3
4
5
6
PUT _cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.disk.watermark.low": "90%"
  }
}

如果只是需要临时修改该值可把上面的persistent替换未transient。如果想永久修改该值可照上面使用persistent做持久处理就可以。

集群中存在多个ES版本

这种情况应该要避免。不只是ES,所有支持集群的其他组件都一样,应保证集群内的所有节点都使用同样的版本。以免引起不可预期的问题。

结论

未分配的分片会直接体现为ES集群的亚健康状态。通过上文了解到索引的分片数设置不当,节点离线或节点磁盘空间不够都会引发产生UNASSIGNED Shard。

虽然说有些情况对服务没有影响,但如果ES看起当前集群不是最佳状态,会通过Shard状态、集群状态给我们一个告警,应给予重视。