运维|ES 的分配统计接口 9.1 起可以取消,这说明它以前有多慢

简介: 9.1 给获取分配统计(allocation stats)的操作加了取消支持。

9.1 给获取分配统计(allocation stats)的操作加了取消支持。单独看是个很小的改动,但把它和同一版本的另一条(cache 支持)放在一起,能看清一件事:这个接口以前有多慢,以及 ES 是怎么一步步改的。

说明

来源和边界先交代:本文的依据出自 v9.4.2 标签下的官方文件(9.1.0.yml、TransportGetAllocationStatsAction.java、CancellableSingleObjectCache.java),文中引用的原文均来自这些文件。我没有启动集群、也没有跑测试,示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

01-9.1.0

图 1:官方文件 9.1.0.yml(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 9.1.0.yml

官方条目

9.1 的 changelog 里有两条相关条目。

取消支持(9.1.0.yml 第 1355 行 @ v9.4.2):

Add cancellation support in `TransportGetAllocationStatsAction`

缓存支持(9.1.0.yml 第 575 行 @ v9.4.2):

Add cache support in `TransportGetAllocationStatsAction`

两条都只有一行 summary。实质内容来自源码。

02-TransportGetAllocationStatsAction

图 2:官方文件 TransportGetAllocationStatsAction.java(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 TransportGetAllocationStatsAction.java

这个接口是干什么的

它对应的是查看集群分片分配情况的 API。用的时候一般是想回答这类问题:

  • 各节点当前有多少分片;
  • 磁盘水位怎么样;
  • 分片分配是否均衡。

监控、巡检、排查磁盘或均衡问题时都会用到。

它慢的原因是:这个统计需要遍历集群状态里的分片信息并汇总。集群状态越大(索引多、分片多),遍历越久。

03-CancellableSingleObjectCache

图 3:官方文件 CancellableSingleObjectCache.java(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 CancellableSingleObjectCache.java

两条改动的关系

这里有个先后问题要先看清楚:

先加缓存(575 行),再加取消(1355 行)。

按常理想,有了缓存之后查询应该变快了,为什么还需要取消?

理解是:缓存解决的是重复查询,取消解决的是首次查询。

  • 缓存命中 → 快;
  • 缓存未命中(首次查询、或缓存失效)→ 还是要完整算一遍。

而完整算一遍在大集群上可能就是几秒甚至更久。这段时间里你想中断它,以前是做不到的。

所以两条改动是互补的:缓存减少需要长时间计算的次数,取消保证偶尔需要的那次不会卡死在那里。

(说明:这个前后关系的判断来自 changelog 里的条目顺序和源码结构,官方没解释过设计顺序,是理解。)

源码里怎么实现的

关键代码在 TransportGetAllocationStatsAction.java 第 99–111 行(源码 @ v9.4.2):

protected void masterOperation(Task task, Request request, ClusterState state, ActionListener<Response> listener) throws Exception {
   
    // NB we are still on a transport thread here - if adding more functionality here make sure to fork to a different pool

    assert task instanceof CancellableTask;
    final var cancellableTask = (CancellableTask) task;

    final SubscribableListener<Map<String, NodeAllocationStats>> allocationStatsStep = request.metrics().contains(Metric.ALLOCATIONS)
        ? SubscribableListener.newForked(l -> allocationStatsCache.get(cancellableTask::isCancelled, l))
        : SubscribableListener.newSucceeded(Map.of());

几个点值得看:

一,assert task instanceof CancellableTask。 这个任务必须是可取消任务。ES 的任务框架里,只有 CancellableTask 才支持被取消。

二,allocationStatsCache.get(cancellableTask::isCancelled, l)。 这是最关键的一行——把"是否被取消"这个函数直接传给了缓存。

这意味着:计算过程中会反复检查有没有被取消。算到一半发现取消了就停,而不是算完再看。

三,只有 ALLOCATIONS 指标走缓存。 metrics().contains(Metric.ALLOCATIONS) 为假时直接返回空 Map,不计算。所以你只查 FS(磁盘)指标的话,不走这条路径。

注释里的关键信息

// NB we are still on a transport thread here - if adding more functionality here make sure to fork to a different pool

大意是:这里还在 transport 线程上,以后加功能记得 fork 到其他线程池。

这是写给后来维护者的提醒。它透露了一件事:transport 线程是宝贵资源,不该在上面跑耗时计算。

所以代码里用了 SubscribableListener.newForked(...)——forked 就是为了把计算挪出 transport 线程。

提这行注释是因为它解释了为什么代码要这么绕:是有约束,不是设计得复杂。

取消机制是怎么工作的

顺着上面的 cancellableTask::isCancelled 再往上一层。

ES 的取消机制大致是这样:

  • 每个可取消请求对应一个 CancellableTask;
  • 取消操作(POST _tasks/<task_id>/_cancel)会置上这个任务的取消标记;
  • 正在执行的代码需要主动检查这个标记,发现被置上就停下来。

关键在最后一点:取消是协作式的。

也就是说:是代码自己定期检查"我被取消了吗",发现是就退出,不是系统强行杀掉线程。

这解释了为什么源码里要把 isCancelled 一路传进缓存的计算逻辑里——没有这层传递,取消标记就没人看,取消也就不会生效。

这对使用者意味着什么

一,你可以中断一个慢的分配统计查询了。 以前发起了就只能等它算完。

二,取消不是瞬时的。 因为是协作式,取消请求发出去之后,要等执行到下一个检查点才会真正停。所以不要期望"点了取消就立刻返回"。

三,取消的粒度取决于检查点密度。 如果检查点稀疏,可能取消前还要多算一会儿。源码层面未逐个确认检查点的具体位置,所以"多久能停下来"这个问题没有准确答案——只能说它会在合理时间内停,不能说会立刻停。

怎么实际用

先看任务列表拿到 task id:

# 发起一个分配统计查询(具体 API 以你版本的文档为准)
curl -s 'localhost:9200/_cat/allocation?v'

# 列出正在执行的任务
curl -s 'localhost:9200/_tasks?detailed=true&actions=*allocation*'

拿到 task id 之后取消:

curl -X POST 'localhost:9200/_tasks/<task_id>/_cancel'

说明:上面 _tasks 的 actions 过滤条件按命名习惯写的,该操作对应的 action 名未核对,实际用的时候可以先不带过滤条件把所有任务列出来再找。

和别的操作对比

ES 里支持取消的操作并不是全部。整理一下常见的几类:

操作 是否可取消 说明
搜索 是 常见场景,支持得较早
分配统计 9.1 起 本篇
强制合并(_forcemerge) 是 长操作,早就需要
快照恢复 是 长操作
部分管理类操作 否 执行快,不需要

规律是:耗时越长的操作,越需要支持取消。 反过来说,看到某个操作支持取消了,可以反推它在某些场景下确实不快。

分配统计被加进来,说明它在某些集群上确实慢到需要中断。这本身也是个信号——如果你发现这个查询经常要跑很久,那更该关注的是集群状态为什么这么大(分片数是不是过多了),而不是怎么取消它。

为什么取消这么难做

前面说"取消是协作式的",听起来简单,但实际上给一个已有操作加取消支持往往不轻松。把难处说清楚,因为这能解释为什么 ES 不是所有操作都支持取消。

一,要在合适的位置插入检查点。 检查点太密 → 每次判断都有开销,影响正常路径的性能;太疏 → 取消后要等很久才停。找一个平衡点需要理解计算过程本身。

二,检查点可能在很深的调用栈里。 从源码能看到,取消标记被一路传进了缓存的 get 方法。也就是说,光在最外层检查是不够的——耗时的循环往往在内部,得把"是否被取消"这件事传递到那里去。

三,取消时不能留下半成品状态。 中途停下意味着有些工作做了一半。要保证集群状态、缓存、监听器都处理得当,不能因为一次取消就留下不一致。

第三点是最难的。这也是为什么"能不能取消"不能事后加个开关就完事——它需要整个执行路径配合。

由此能得出一个判断:如果某个操作不支持取消,多半是做起来代价高,不是没想到。遇到时不要指望"加个参数"就能解决。

为什么要单独关注分片数

前面提到"这个查询慢"是个信号,指向分片数。这里说具体一点,因为它是本篇最有实操价值的部分。

分片过多的典型表现:

  • 集群状态变大 → master 节点内存压力大、状态发布变慢;
  • 分配统计、cat API 这类需要遍历的操作变慢(本篇);
  • 索引恢复变慢,节点重启后要等很久;
  • 单个分片很小,资源利用率低。

怎么判断自己是不是分片过多:常见的经验值是堆内存每 GB 对应 20 个左右分片,但这个数字随版本和场景变化,不建议当硬性标准。更可靠的做法是看趋势:分片数增长速度是否远超数据量增长。

如果确实多了,能做的事:

  • 合并小索引(用 _shrink 或重建);
  • 调整索引生命周期策略,减少单索引的分片数;
  • 用数据流 + rollover 控制单个索引的大小。

不要做的事:看到慢就去调超时或依赖取消。那是缓解症状,不是解决原因。

把这节写进来,是因为本篇的改动本质上是"ES 帮你应对一个问题",而那个问题本身才是该解决的。取消功能是安全带,不是方向盘——它能让你从卡住的查询里脱身,但不能让集群变健康。

这条改动的价值不在"能取消"

说实话,能取消一个查询,不是什么重要能力。真正有价值的信息是它的前提——这个操作慢到需要被取消。

顺着这个前提想,排查方向就变了:

  • 如果这个查询在你的集群上从来没超过几百毫秒 → 这条改动对你没意义;
  • 如果经常要几秒甚至更久 → 该查的是"分片数是不是太多了",而不是依赖取消功能。

分片数是集群健康度的基础指标。 分片过多会带来一系列问题:集群状态膨胀、master 压力大、恢复变慢。分配统计变慢只是其中一个表现。

所以建议是:把这条改动当成一个提示,而不是一个功能。它提示你"这个操作可能很慢",而慢的原因通常值得查——查清楚之后,取消功能就用不上了。

写在最后

很小的改动:一个操作从不可取消变成可取消。

一,取消支持要配合同版本的缓存支持一起看。 缓存管重复查询,取消管首次查询,两条是互补的——少了一条,另一条都补不上那个洞。

二,取消是协作式的。 代码要主动检查取消标记才会停,所以不是瞬时的。源码里 allocationStatsCache.get(cancellableTask::isCancelled, l) 就是在做这层传递——把"是否被取消"一路传进计算内部。

三,只有 ALLOCATIONS 指标走这条路径。 只查磁盘指标时不涉及,也就没有取消的必要——因为它不慢。

另外诚实标注了几处:"先缓存后取消"的关系是推断(依据条目顺序和源码结构,官方没解释);检查点的具体位置和密度未逐个确认,所以无法回答"取消后多久能停";该操作对应的 task action 名未核对,正文里 _tasks 的过滤条件按命名习惯写的。

参考

本文对照 Elasticsearch v9.4.2 标签的 changelog 与源码核对,引文与代码逐字取自原文。"先加缓存后加取消"的前因关系是理解,官方未解释设计顺序;取消检查点的位置与密度未逐个确认,故未给出"取消后多久停止"的结论;该操作对应的 task action 名未核对,正文 _tasks 的 actions 过滤条件按命名习惯书写。撰写时未启动集群、未运行测试。

目录
相关文章
|
5天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
2203 13
|
云安全 安全 网络安全
|
5天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
968 1
|
18天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
3119 49
|
12天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2109 2
|
5天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
|
10天前
|
人工智能 JSON Linux
【保姆级】ComfyUI部署教程(下载+安装+配置+工作流,2026最新)
ComfyUI 是一款开源节点式AI生成工具,支持图像、视频、音频及3D内容创作。通过拖拽连线组合模型加载、提示词、采样等节点,构建可复用、易修改的工作流,兼容SD1.5、SDXL、Flux、Qwen Image等主流模型。
|
4天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)