ES 超时从 5xx 改成 429,按 5xx 重试的代码会以为服务端挂了

简介: 9.0 起 Elasticsearch 的超时异常从 5xx 变成 429(Too Many Requests)。

9.0 起 Elasticsearch 的超时异常从 5xx 变成 429(Too Many Requests)。这条 breaking change 不报错、不改数据,但会让"按 5xx 判服务端故障"的客户端、告警规则和重试逻辑全部误判。

说明

实测环境:本机保留带 v5.6.16 / v7.3.0 / v7.4.0 / v8.17.0 / v8.18.0 / v9.0.0 / v9.1.0 / v9.2.0 / v9.3.0 / v9.4.2 等标签的上游源码仓库 elasticsearch/;用 git -C elasticsearch 的 show / diff / ls-tree 读指定标签的源码、提交 diff 与文件清单。另起一个 9.4.2 单节点实例(HTTP 9200)用于出图框架。文末实测小节里的命令与响应均取自本机。

一个状态码改动,影响面比看起来大

一个状态码的改动,影响面比看起来大:

  • 客户端里 if (status >= 500) 判定服务端故障 的逻辑,从此不再覆盖超时;
  • 反过来,把 429 一律当成"被限流、等一会儿再试"的代码,现在会把等待超时也当成限流;
  • 告警规则如果按 5xx 计数,"集群里超时变多"这件事会从监控里消失。

这条改动官方只给了一句说明,没有细节,所以直接去源码里对了一遍。

官方依据

9.0 的 breaking changes 列表里这一条(docs/release-notes/breaking-changes.md @ v9.4.2):

304: * Change Elasticsearch timeouts to 429 response instead of 5xx [#116026]

v9.4.2:docs/release-notes/breaking-changes.md#L304——就这一行,没给原因也没给范围。所以下面全靠代码核实。

代码对照:8.18 与 9.4.2 的差异

超时异常是 ElasticsearchTimeoutException。先看 8.18(server/src/main/java/org/elasticsearch/ElasticsearchTimeoutException.java @ v8.18.0):

public class ElasticsearchTimeoutException extends ElasticsearchException {
   
    public ElasticsearchTimeoutException(StreamInput in) throws IOException {
    super(in); }
    public ElasticsearchTimeoutException(Throwable cause) {
    super(cause); }
    public ElasticsearchTimeoutException(String message, Object... args) {
    super(message, args); }
    public ElasticsearchTimeoutException(String message, Throwable cause, Object... args) {
   
        super(message, cause, args);
    }
}

没有覆写 status()——所以它继承 ElasticsearchException.status() 的默认值,也就是 500(INTERNAL_SERVER_ERROR)。这就是"以前是 5xx"的来源。

再看 9.4.2(同文件 @ v9.4.2):

40:    public RestStatus status() {
   
42:        return RestStatus.TOO_MANY_REQUESTS;
43:    }
46:    public boolean isTimeout() {
   

两个变化:

  • 第 40–42 行:显式覆写 status(),返回 RestStatus.TOO_MANY_REQUESTS;
  • 第 46 行:新增 isTimeout(),让调用方能直接判断"这是超时",不用去猜状态码。

TOO_MANY_REQUESTS 的定义在 v9.4.2:server/src/main/java/org/elasticsearch/rest/RestStatus.java#L468:TOO_MANY_REQUESTS(429)——这个枚举值 8.18 和 9.4.2 里都有(同样在 468 行),变的只是超时异常不再走默认 500。

为什么是 429

官方没说,判断是语义归位:超时是"服务端在规定时间内没干完",属于可重试的临时状态,和限流(429 Too Many Requests,标准语义里就带"你可以稍后重试")是一类;而 5xx 的语义是"服务端出错",客户端通常不会重试,还会把它算进故障率。以前把超时归到 5xx,等于让一堆可重试的场景被当成故障。

需要说明:这段是推断,不是官方文档的说法,判断依据是 HTTP 状态码的语义分工——结论本身(5xx→429)是核实过的,原因是补的解释。

哪些路径会拿到 429

凡是抛 ElasticsearchTimeoutException 的地方,HTTP 状态码都从 5xx 变成 429。在 v9.4.2 里 grep 了这个异常的引用位置,能核实到的有:

文件 场景
org/elasticsearch/action/support/ListenerTimeouts.java 请求 listener 的超时控制
org/elasticsearch/action/support/SubscribableListener.java listener 超时
org/elasticsearch/cluster/service/ClusterApplierRecordingService.java(L103) 集群状态应用没在规定时间完成
org/elasticsearch/action/admin/cluster/configuration/TransportAddVotingConfigExclusionsAction.java 投票配置排除的等待超时
org/elasticsearch/action/admin/cluster/configuration/TransportClearVotingConfigExclusionsAction.java 同上,清理分支
org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java field_caps 等待超时
org/elasticsearch/action/support/single/shard/TransportSingleShardAction.java 单分片操作(get、termvectors 一类)
org/elasticsearch/common/util/concurrent/FutureUtils.java 通用 future 超时

一条重要的边界:搜索请求自带的 timeout 参数(以及 search.default_search_timeout)不在这个异常的抛出路径上——grep action/search/ 目录没有命中 ElasticsearchTimeoutException,搜索超时的表现是返回已经收集到的部分结果、响应里带 timed_out: true,HTTP 状态码仍是 200。

这条未逐个接口实测,落到具体场景上,建议自己确认一次:造一个会超时的小请求,看返回的响应码是不是 429,而不要只看客户端日志里的报错文案。

客户端要改什么

1. 判断逻辑从"5xx 即故障"改成"先看是不是超时"。 服务端现在给了更准的信号:

{
   
  "error": {
    "type": "timeout_exception", "reason": "..." },
  "status": 429
}

判断顺序建议是:先判 status == 429,再看 is_timeout / 异常类型是不是 timeout_exception,最后才落到通用 5xx 分支。

2. 重试策略要分开。 超时和限流都是 429,但处置方式不一样:

  • 限流(EsRejectedExecutionException,见下节):退避重试,同时该考虑降压;
  • 超时:重试是有用的,但先确认超时阈值合不合理——一直超时的重试只会把压力放大。

3. 告警规则要补一个维度。 如果告警是按 HTTP 状态码分桶的,超时会从 5xx 桶里消失、混进 429 桶(和限流一起)。建议把 429 拆成"限流 429"和"超时 429"两个指标再设阈值,否则限流告警会被超时稀释。

429 和限流怎么区分

同一个 429 现在代表两件事,这是升级后最容易踩的坑。看 v9.4.2:server/src/main/java/org/elasticsearch/ExceptionsHelper.java#L88-L89:

} else if (t instanceof EsRejectedExecutionException) {
   
    return RestStatus.TOO_MANY_REQUESTS;
}

也就是说,队列打满被拒绝(EsRejectedExecutionException)一直是 429,现在超时也变成 429。区分办法是看 error.type:

场景 异常类 error.type
被限流/队列满 EsRejectedExecutionException es_rejected_execution_exception
等待超时 ElasticsearchTimeoutException timeout_exception

别只看状态码。

在本地仓库上跑一遍

正文说 9.0 起超时返回 429,把两个标签的异常类读一次就能对上。

01-timeout-status

图 1:本机源码标签的实测输出。ElasticsearchTimeoutException.java 在 v8.18.0 里没有覆写 status()(沿用父类 5xx);v9.0.0 与 v9.4.2 都新增 public RestStatus status() 并返回 RestStatus.TOO_MANY_REQUESTS。

P=server/src/main/java/org/elasticsearch/ElasticsearchTimeoutException.java
printf 'v8.18.0 status() 命中 -> '; git -C elasticsearch show "v8.18.0:${P}" | grep -c 'RestStatus status()'
# 0
for t in v9.0.0 v9.4.2; do printf '%s -> ' "$t"; \
  git -C elasticsearch show "${t}:${P}" | grep 'return RestStatus' | sed 's/^ *//'; done
# v9.0.0 -> return RestStatus.TOO_MANY_REQUESTS;
# v9.4.2 -> return RestStatus.TOO_MANY_REQUESTS;
curl -s 'http://localhost:9200/' | jq -r '.version.number'
# 9.4.2

图 1 把 changelog 里那句「超时从 5xx 改成 429」落到了代码:旧类没有 status() 覆写,新类直接返回 TOO_MANY_REQUESTS,到 9.4.2 仍然如此。按 5xx 判服务端故障的客户端逻辑因此需要改,这一点正文已经展开。

边界:

  • 图 1 出自本机源码标签,没有起集群、没有发起过会超时的请求,也未验证实际 HTTP 响应码;
  • 「为什么改成 429」是推断,「搜索 timeout 不受影响」未逐个接口实测;
  • 客户端侧的重试与告警误判未模拟。

写在最后

  • 这条 breaking change 的杀伤力在于它很安静:没有报错、没有数据影响,只是状态码语义变了,所以升级后没人第一时间发现,直到告警曲线和重试行为开始对不上。
  • 改客户端的三件事:判断顺序先 429 后 5xx、重试策略把超时和限流分开、告警把 429 拆成两个指标。
  • 别忘了一条边界:搜索的 timeout 不在这次改动里,它照旧返回 200 + timed_out: true。

参考

本文所有代码引用均取自本地 elasticsearch/ 仓库 v9.4.2 / v8.18.0 标签,行号已逐条核对。文末实测小节里已实际对照 ElasticsearchTimeoutException:v8.18.0 无 status() 覆写,v9.0.0 与 v9.4.2 返回 RestStatus.TOO_MANY_REQUESTS。本机未发起过超时请求,实际 HTTP 响应码未验证;"为什么改成 429"是推断;"搜索 timeout 不受影响"未逐个接口实测。

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