在 Elasticsearch 中,一个健康的集群一定是平衡的:主分片(master)和副本分片(replica)分布在所有节点上,以便在节点发生故障时依旧保证可用性。
当时当你看到分片在一个 UNASSIGNED
状态中时,你应该怎么做?
在我们深入探究一些解决方案之前,让我们验证未分配(UNASSIGNED
)的分片是否包含我们需要保留的数据(如果没有,删除这些分片是解决问题的最直接方法)。如果您已经确认该分片存在有价值的数据,请跳转至解决方案:
本文汇总的命令格式假设您在默认端口(9200)上运行每个 Elasticsearch 实例的 HTTP 服务。我们假设您在本地提交请求,所以它们被重定向到 localhost
;如果不是,您替换 localhost
为您节点的 IP 地址。
查明有问题的分片
Elasticsearch 的 cat shards API 会告诉您哪些分片未分配,以及未分配的原因:
curl -XGET localhost:9200/_cat/shards?h=index,shard,prirep,state,unassigned.reason| grep UNASSIGNED
每行列出索引的名称、分片编号、是主分片(p)还是副本(r)分片,以及未分配的原因:
constant-updates 0 p UNASSIGNED NODE_LEFT node_left
如果您运行的是 Elasticsearch 5+ 版本,您还可以使用 cluster allocation explain API 来尝试获取有关分片分配问题的更多信息:
curl -XGET localhost:9200/_cluster/allocation/explain?pretty
生成的输出将提供有用的详细信息,说明集群中的某些分配仍未分配:
{
"index" : "testing",
"shard" : 0,
"primary" : false,
"current_state" : "unassigned",
"unassigned_info" : {
"reason" : "INDEX_CREATED",
"at" : "2018-04-09T21:48:23.293Z",
"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" : "t_DVRrfNS12IMhWvlvcfCQ",
"node_name" : "t_DVRrf",
"transport_address" : "127.0.0.1:9300",
"node_decision" : "no",
"weight_ranking" : 1,
"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"
}
]
}
]
}
在这种情况下,API 清楚地解释了为什么副本分配保持未分配:"the shard cannot be allocated to the same node on which a copy of the shard already exists(分片不能分配给已经存在分配副本的统一节点)"。要查看有关此特定问题的更多详细信息以及如何解决它,请 跳转至 本文后面的部分。
如果未分配的分片属于您认为已删除的索引,或者您不再需要的过时索引,则可以删除索引以将集群状态恢复绿色:
curl -XDELETE 'localhost:9200/index_name/'
如果这不能解决问题,请继续阅读以尝试其他解决方案。
原因一:故意延迟分片分配
当一个节点离开集群时,主节点会暂时延迟分片重新分配,以表面原始节点能够在一定时间内(默认为一分钟)恢复,如果在此期间重新平衡分片,会造成不必要的资源浪费。如果是这种情况,您的日志应如下所示:
[TIMESTAMP][INFO][cluster.routing] [PRIMARY NODE NAME] delaying allocation for [54] unassigned shards, next check in [1m]
您可以像这样动态修改延迟时间:
curl -XPUT "localhost:9200/<INDEX_NAME>/_settings?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}'
替换 <INDEX_NAME>
为 _all
将更新集群中所有索引的阈值。
延迟时间结束后,您应该开始看到主节点分配了这些分片。如果没有,请继续阅读以探索其他潜在原因的解决方案。
原因二:分片太多,节点不够
当节点加入和离开集群时,主节点会自动重新分配分片,确保一个分片的多个副本不会分配给同一个节点。换句话说,主节点不会将主分片分配给与其副本相同的节点,也不会将同一分片的两个副本分配给同一个节点。如果没有足够的节点来相应地分配分片,分配可能会停留在未分配状态。
为避免此问题,请按照以下公式确保集群中的每个索引都初始化,且每个主分片的副本数少于集群中的节点数:
N >= R + 1
其中 N 是集群中的节点数,R 是集群中所有索引的最大分片副本因子。
在下面的屏幕截图中,many-shards
索引存储在四个主分片上,每个主分片有四个副本。索引的 20 个分片中有 8 个未分配,因为我们的集群仅包含三个节点,每个主分片的两个副本尚未分配,因为三个节点中的每个都已包含该分片的副本。
要解决此问题,您可以向集群添加更多数据节点或减少副本数量。在我们的示例中,无门需要在集群中至少再添加两个节点或者将副本因子较少到两个,如下所示:
curl -XPUT "localhost:9200/<INDEX_NAME>/_settings?pretty" -H 'Content-Type: application/json' -d' { "number_of_replicas": 2 }'
减少副本数量后,看看是否已分配所有分片
原因三:您需要重新启用分片分配
在下面的 Kopf 屏幕截图中,一个节点刚刚加入集群,但尚未分配任何分片。
默认情况下在所有节点上启用 分片分配,但您可能在某些时候禁用了分片分配(例如,为了执行 滚动重启 ),并且忘记重启启用它。
要启用分片分配,请更新集群更新设置API :
curl -X PUT "localhost:9200/_cluster/settings?pretty" -H 'Content-Type: application/json' -d'
{
"transient" : {
"cluster.routing.allocation.enable" : "all"
}
}
'
如果这解决了问题,您的 Kopf 或 Datadog dashboard 应显示未分配分片数量随着它们成功分配给节点而减少。
此 Datadog 时间序列图显示重新启用分片分配后未分配的分片数量减少。
更新后的 Kopf 仪表盘显示,在重新启用分片分配后,许多以前未分配的分片已被分配。
看起来这解决了我们所有未分配的分片的问题,但有一个例外:constant-updates
索引的分片 0。让我们探讨一下为什么分片仍未分配的其他可能原因。
原因四:集群中不再存在 Shard 数据
constant-updates
在这种情况下,索引的主分片 0 未分配。它可能是在没有任何副本的节点上创建的(一种用于加速初始索引 过程的技术),并且该节点在数据被复制之前就离开了集群。主节点在其全局集群状态文件中检测到分片,但无法在集群中定位分片的数据。
另一种可能性是节点在重新启动时可能遇到了问题。通常,当一个节点恢复与集群的连接时,它会将有关其磁盘上的分片信息转发给主节点,然后主节点将这些分片从 "unassigned" 过渡到 "assigned/started"。当这个进程由于某种原因失败时(例如节点的存储以某种方式被破坏),分片可能仍然未分配。
在这种情况下,您必须决定如何继续:尝试让原始节点恢复并重新加入集群(不要执行强制分配主分片),或使用 集群重路由API强制分配分片并使用原始数据源或备份文件重新索引丢失的数据。
如果您决定使用后者(强制分配分片),需要注意的是您将分配一个"空"分片。如果包含原始主分片数据的节点稍后重新加入集群,则其数据将被新创建的(空)主分片覆盖,因为它将被视为数据的"更新"版本。再继续此操作之前,您可能希望重新分配,这将允许您保留存储在该分片上的数据。
如果您了解其中的含义,并且仍想强制分配未分配的主分片,则可以使用 allocate_empty_primary
标志来执行此操作。以下命令将 constant-updates
索引中的主分片 0 重新路由到特定节点:
curl -XPOST "localhost:9200/_cluster/reroute?pretty" -H 'Content-Type: application/json' -d'
{
"commands" : [
{
"allocate_empty_primary" : {
"index" : "constant-updates",
"shard" : 0,
"node" : "<NODE_NAME>",
"accept_data_loss" : "true"
}
}
]
}
'
请注意,您需要指定"accept_data_loss":"true"
以确保您已准备好丢失分片上的数据。如果不包含此参数,您将看到如下错误:
{
"error" : {
"root_cause" : [
{
"type" : "remote_transport_exception",
"reason" : "[NODE_NAME][127.0.0.1:9300][cluster:admin/reroute]"
}
],
"type" : "illegal_argument_exception",
"reason" : "[allocate_empty_primary] allocating an empty primary for [constant-updates][0] can result in data loss. Please confirm by setting the accept_data_loss parameter to true"
},
"status" : 400
}
您现在需要重新索引丢失的数据,或使用 Snapshot and Restore API 从备份快照中尽可能多地恢复。
原因五:磁盘水位过低
如果没有足够的节点和足够的磁盘空间,主节点可能无法分配分片(它不会将分片分配给磁盘使用率超过85%的节点)。一旦一个节点达到了这个磁盘使用水平,或者 Elasticsearch 称之为 "low disk watermark",他就不会被分配更多的分片。
您可以通过查询 cat API 检查集群中每个节点上的磁盘空间(并查看每个节点上存储了哪些分片):
curl -s 'localhost:9200/_cat/allocation?v'
如果任何特定节点的磁盘空间不足(删除过时的数据并将其存储在集群外、添加更多节点、升级硬件等),请参阅这篇文件以获取有关如何操作的选项 。
如果您的节点具有较大的磁盘容量,则默认的低水位线(85%的磁盘使用率)可能太低。您可以使用集群更新设置API 来更改 cluster.routing.allocation.disk.watermark.low
和/或 cluster.routing.allocation.disk.watermark.high
。例如,这个 Stack Overflow thread 指出,如果您的节点有 5TB 的磁盘容量,您可能可以安全地 增加低磁盘水位 到 90%:
curl -XPUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
"transient": {
"cluster.routing.allocation.disk.watermark.low": "90%"
}
}'
如果您希望配置更改在集群重新启动时保持不变,请将 "transient" 替换为 "persistent",或在配置文件中更新这些值。您可以选择使用字节或百分比值来更新这些设置,但请务必记住Elasticsearch文档 中的这一重要说明:“百分比值指 使用的 磁盘空间,而字节值是指 剩余 磁盘空间”。
原因六:多个 Elasticsearch 版本
这个entinel只出现在运行多个 Elasticsearch 版本的集群中(可能在滚动升级的期间)。根据Elasticsearch文档,主节点不会将主分片的副本分配给任何运行旧版本的节点。例如,如果主分片在 1.4 版本上运行,则主节点将无法将该分片的副本分配给运行 1.4 之前的任何版本的任何节点。
如果您尝试手动将分片从较新版本的节点重新路由到较旧版本的节点,您将看到如下错误:
[NO(target node version [XXX] is older than source node version [XXX])]
Elasticsearch 不支持回滚 到以前的版本,只支持升级。如果这确实是手头的问题,升级运行旧版本的节点应该可以解决问题。
你试过把它关掉再打开吗?
如果上述情况均不适用您的情况,您仍然可以选择从原始数据与重新索引丢失的数据,或从旧快照恢复受影响的索引,参考链接。