热点

编辑

当资源利用在节点之间分布不均时,Elasticsearch 中可能会出现计算机热点现象。临时的峰值通常不被认为是问题,但持续显著的独特利用率可能会导致集群瓶颈,应进行审查。

检测热点

编辑

热点最常见的表现形式是在cat nodes报告中,一部分节点的资源利用率(disk.percentheap.percentcpu)显著升高。个别的峰值不一定有问题,但如果利用率反复飙升或长时间保持高位(例如,超过 30 秒),则该资源可能正在经历严重的热点问题。

例如,让我们使用 cat nodes 展示两个单独的合理问题

resp = client.cat.nodes(
    v=True,
    s="master,name",
    h="name,master,node.role,heap.percent,disk.used_percent,cpu",
)
print(resp)
response = client.cat.nodes(
  v: true,
  s: 'master,name',
  h: 'name,master,node.role,heap.percent,disk.used_percent,cpu'
)
puts response
const response = await client.cat.nodes({
  v: "true",
  s: "master,name",
  h: "name,master,node.role,heap.percent,disk.used_percent,cpu",
});
console.log(response);
GET _cat/nodes?v&s=master,name&h=name,master,node.role,heap.percent,disk.used_percent,cpu

假设在五分钟内拉取了两次相同的输出

name   master node.role heap.percent disk.used_percent cpu
node_1 *      hirstm              24                20  95
node_2 -      hirstm              23                18  18
node_3 -      hirstmv             25                90  10

在这里,我们看到两种显著的独特利用率:主节点cpu: 95,以及热节点disk.used_percent: 90%。这表明这两个节点上正在发生热点问题,并且不一定来自相同的根本原因。

原因

编辑

历史上,集群主要由于硬件、分片分布和/或任务负载的影响而出现热点问题。我们将按其潜在影响范围的顺序依次审查这些问题。

硬件

编辑

以下是一些可能导致热点问题的常见不正确的硬件设置

  • 资源分配不均匀。例如,如果一个热节点的 CPU 只有其对等节点的一半。Elasticsearch 希望数据层上的所有节点共享相同的硬件配置文件或规格。
  • 主机上的另一个服务(包括其他 Elasticsearch 节点)消耗资源。请参阅我们的专用主机建议。
  • 资源经历了不同的网络或磁盘吞吐量。例如,如果一个节点的 I/O 低于其对等节点。有关更多信息,请参阅使用更快的硬件
  • JVM 配置的堆大小超过 31GB。有关更多信息,请参阅设置 JVM 堆大小
  • 有问题资源唯一报告内存交换

分片分布

编辑

Elasticsearch 索引被分成一个或多个分片,这些分片有时分布不均。Elasticsearch 通过跨数据节点平衡分片计数来解决此问题。正如在 8.6 版本中引入的那样,Elasticsearch 默认情况下还启用了期望的平衡来考虑摄取负载。节点仍然可能由于写入密集型索引或其托管的总体分片而出现热点。

节点级别
编辑

您可以通过cat allocation来检查分片平衡,但是从 8.6 版本开始,期望的平衡可能不再完全期望平衡分片。请注意,这两种方法都可能在集群稳定性问题期间暂时显示出问题的不平衡。

例如,让我们使用 cat allocation 展示两个单独的合理问题

resp = client.cat.allocation(
    v=True,
    s="node",
    h="node,shards,disk.percent,disk.indices,disk.used",
)
print(resp)
response = client.cat.allocation(
  v: true,
  s: 'node',
  h: 'node,shards,disk.percent,disk.indices,disk.used'
)
puts response
const response = await client.cat.allocation({
  v: "true",
  s: "node",
  h: "node,shards,disk.percent,disk.indices,disk.used",
});
console.log(response);
GET _cat/allocation?v&s=node&h=node,shards,disk.percent,disk.indices,disk.used

可能返回

node   shards disk.percent disk.indices disk.used
node_1    446           19      154.8gb   173.1gb
node_2     31           52       44.6gb   372.7gb
node_3    445           43      271.5gb   289.4gb

在这里,我们看到了两种显著的独特情况。node_2最近已重新启动,因此它的分片数量远低于所有其他节点。这也与disk.indices远小于disk.used有关,因为分片正在通过cat recovery进行恢复。尽管node_2的分片计数很低,但由于正在进行的ILM 滚动,它可能成为写入热点。这是下一节中介绍的写入热点的常见根本原因。

第二种情况是,即使node_3node_1拥有的分片数量大致相同,但它的disk.percent高于node_1。当分片大小不均匀时(请参阅目标是分片最多 2 亿个文档,或者大小在 10GB 到 50GB 之间)或当有很多空索引时,会发生这种情况。

基于期望平衡的集群重新平衡在防止节点出现热点方面做了大量工作。它可以受到节点达到水位线(请参阅修复磁盘水位线错误)的限制,或者受到写入密集型索引的总分片数远低于写入节点的限制。

您可以通过节点统计信息 API来确认热点节点,可以多次轮询以仅检查它们之间的统计信息差异,而不是轮询一次以获取节点完整节点正常运行时间的统计信息。例如,要检查所有节点的索引统计信息

resp = client.nodes.stats(
    human=True,
    filter_path="nodes.*.name,nodes.*.indices.indexing",
)
print(resp)
response = client.nodes.stats(
  human: true,
  filter_path: 'nodes.*.name,nodes.*.indices.indexing'
)
puts response
const response = await client.nodes.stats({
  human: "true",
  filter_path: "nodes.*.name,nodes.*.indices.indexing",
});
console.log(response);
GET _nodes/stats?human&filter_path=nodes.*.name,nodes.*.indices.indexing
索引级别
编辑

热点节点经常通过cat thread poolwritesearch队列备份来表面化。例如

resp = client.cat.thread_pool(
    thread_pool_patterns="write,search",
    v=True,
    s="n,nn",
    h="n,nn,q,a,r,c",
)
print(resp)
response = client.cat.thread_pool(
  thread_pool_patterns: 'write,search',
  v: true,
  s: 'n,nn',
  h: 'n,nn,q,a,r,c'
)
puts response
const response = await client.cat.threadPool({
  thread_pool_patterns: "write,search",
  v: "true",
  s: "n,nn",
  h: "n,nn,q,a,r,c",
});
console.log(response);
GET _cat/thread_pool/write,search?v=true&s=n,nn&h=n,nn,q,a,r,c

可能返回

n      nn       q a r    c
search node_1   3 1 0 1287
search node_2   0 2 0 1159
search node_3   0 1 0 1302
write  node_1 100 3 0 4259
write  node_2   0 4 0  980
write  node_3   1 5 0 8714

在这里,您可以看到两种显著的独特情况。首先,与其他节点相比,node_1的写入队列严重积压。其次,node_3显示的历史完成写入是其他任何节点的两倍。这些都可能是由于写入密集型索引分布不均,或者多个写入密集型索引分配给同一节点。由于主副本写入基本上是相同的集群工作量,我们通常建议设置index.routing.allocation.total_shards_per_node,以便在将索引分片计数与总节点数对齐后强制进行索引传播。

我们通常建议重写入索引具有足够的主number_of_shards和副本number_of_replicas,以便在索引节点之间均匀分布。或者,您可以重新路由分片到更安静的节点,以缓解具有写入热点的节点。

如果索引不明显,您可以通过运行索引统计信息 API进行进一步内省

resp = client.indices.stats(
    level="shards",
    human=True,
    expand_wildcards="all",
    filter_path="indices.*.total.indexing.index_total",
)
print(resp)
response = client.indices.stats(
  level: 'shards',
  human: true,
  expand_wildcards: 'all',
  filter_path: 'indices.*.total.indexing.index_total'
)
puts response
const response = await client.indices.stats({
  level: "shards",
  human: "true",
  expand_wildcards: "all",
  filter_path: "indices.*.total.indexing.index_total",
});
console.log(response);
GET _stats?level=shards&human&expand_wildcards=all&filter_path=indices.*.total.indexing.index_total

对于更高级的分析,您可以轮询分片级统计信息,这使您可以比较联合索引级和节点级统计信息。此分析不会考虑节点重新启动和/或分片重新路由,但可作为概述。

resp = client.indices.stats(
    metric="indexing,search",
    level="shards",
    human=True,
    expand_wildcards="all",
)
print(resp)
response = client.indices.stats(
  metric: 'indexing,search',
  level: 'shards',
  human: true,
  expand_wildcards: 'all'
)
puts response
const response = await client.indices.stats({
  metric: "indexing,search",
  level: "shards",
  human: "true",
  expand_wildcards: "all",
});
console.log(response);
GET _stats/indexing,search?level=shards&human&expand_wildcards=all

例如,您可以使用第三方 JQ 工具来处理保存为indices_stats.json的输出

cat indices_stats.json | jq -rc ['.indices|to_entries[]|.key as $i|.value.shards|to_entries[]|.key as $s|.value[]|{node:.routing.node[:4], index:$i, shard:$s, primary:.routing.primary, size:.store.size, total_indexing:.indexing.index_total, time_indexing:.indexing.index_time_in_millis, total_query:.search.query_total, time_query:.search.query_time_in_millis } | .+{ avg_indexing: (if .total_indexing>0 then (.time_indexing/.total_indexing|round) else 0 end), avg_search: (if .total_search>0 then (.time_search/.total_search|round) else 0 end) }'] > shard_stats.json

# show top written-to shard simplified stats which contain their index and node references
cat shard_stats.json | jq -rc 'sort_by(-.avg_indexing)[]' | head

任务负载

编辑

如上文cat thread pool示例中所见,分片分布问题很可能表现为任务负载。任务也有可能由于单个定性成本或总体定量流量负载而在节点上形成热点。

例如,如果cat thread pool报告warmer线程池上的高队列,您将查找受影响节点的热线程。假设它报告warmer线程在100% cpuGlobalOrdinalsBuilder相关。这将让您知道要检查字段数据的全局序数

或者,假设cat nodes显示热点主节点,并且cat thread pool显示跨节点的常规排队。这表明主节点不堪重负。要解决此问题,首先确保硬件高可用性设置,然后查找短暂的原因。在此示例中,节点热线程 API报告other中的多个线程,这表示它们正在等待或被垃圾回收或 I/O 阻塞。

对于这两种示例情况,确认有问题任务的好方法是通过cat 任务管理查看运行时间最长的非连续(指定为[c])任务。这可以通过cat 挂起任务检查运行时间最长的集群同步任务来补充。使用第三个示例,

resp = client.cat.tasks(
    v=True,
    s="time:desc",
    h="type,action,running_time,node,cancellable",
)
print(resp)
response = client.cat.tasks(
  v: true,
  s: 'time:desc',
  h: 'type,action,running_time,node,cancellable'
)
puts response
const response = await client.cat.tasks({
  v: "true",
  s: "time:desc",
  h: "type,action,running_time,node,cancellable",
});
console.log(response);
GET _cat/tasks?v&s=time:desc&h=type,action,running_time,node,cancellable

这可能返回

type   action                running_time  node    cancellable
direct indices:data/read/eql 10m           node_1  true
...

这表明存在一个有问题的EQL 查询。我们可以通过任务管理 API进一步了解它

resp = client.tasks.list(
    human=True,
    detailed=True,
)
print(resp)
response = client.tasks.list(
  human: true,
  detailed: true
)
puts response
const response = await client.tasks.list({
  human: "true",
  detailed: "true",
});
console.log(response);
GET _tasks?human&detailed

其响应包含一个description,报告此查询

indices[winlogbeat-*,logs-window*], sequence by winlog.computer_name with maxspan=1m\n\n[authentication where host.os.type == "windows" and event.action:"logged-in" and\n event.outcome == "success" and process.name == "svchost.exe" ] by winlog.event_data.TargetLogonId

这让您知道要检查哪些索引(winlogbeat-*,logs-window*),以及EQL 搜索请求正文。最有可能的是,这与SIEM 相关。您可以根据需要将其与审计日志记录结合使用,以跟踪请求源。