5.0 起 Elasticsearch 不再忽略无法识别的 URL 查询参数,请求会直接失败;
_id超过 512 字节被拒绝;/_optimize端点删除改用/_forcemerge(且不再支持 GET);创建索引只接受 PUT;类型存在性检查从HEAD {index}/{type}改成HEAD {index}/_mapping/{type};/_cluster/stats的mem段和节点角色统计结构都变了。同一页还有禁用未加引号的 JSON、Analyze API 参数改名等条目。本文对照 v5.6.16 源码把报错原文和长度限制的实现位置写出来。
说明
实测环境:本地起的 Elasticsearch 5.6.16 单节点实例(官方 tar 包直接跑,堆内存 512MB,外挂 JDK 8),curl 连本地 9200 端口。本文「实测」小节的命令和输出都出自这个实例;文档与源码依据仍按 v5.6.16 标签下的 rest.asciidoc、BaseRestHandler.java、IndexRequest.java 引用,哪些是文档写明、哪些是我的推断,正文里逐处标注。
图 1:官方文件 rest.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 rest.asciidoc
严格解析 URL 查询参数
这是 5.0 REST 层最影响面最广的行为变更(migrate_5_0/rest.asciidoc 第 4–12 行):
Previous versions of Elasticsearch ignored unrecognized URL query string parameters. This means that extraneous parameters or parameters containing typographical errors would be silently accepted by Elasticsearch. This is dangerous from an end-user perspective because it means a submitted request will silently execute not as intended. This leniency has been removed and Elasticsearch will now fail any request that contains unrecognized query string parameters.
以前写错参数名(比如 ?refresh=true 写成 ?refesh=true)会被静默忽略,请求按"没有这个参数"执行。官方把这类问题定性为危险:用户以为请求按自己的意思执行了,实际上没有。5.0 起直接拒绝。
报错的构造在 BaseRestHandler 第 88–119 行。消息格式是:
request [/_search] contains unrecognized parameter: [xxx] -> did you mean [yyy]?
实现里对每个不认识的参数跑一遍 Levenshtein 距离,超过 0.7 的候选会作为"你是不是想写"列出来,找不到候选时就没有 -> did you mean 这一段。这个建议值得看一眼,它通常会直接指出正确拼写。
这条和 5.0 的"所有设置必须已知"是同一个思路:把静默失败变成显式失败。区别是作用对象:设置校验管配置文件(节点启动),这条管 URL 查询串(每个请求)。请求体里 JSON 字段的严格解析是另一套机制,不受这条影响。
图 2:官方文件 BaseRestHandler.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 BaseRestHandler.java
改报错不等于恢复原状
升级后最常见的处理动作是"照报错把参数名改对"。这一步在多数情况下是对的,但要意识到:拼错的参数在旧版本里并没有生效,改对之后它才开始生效。
比如原来写 ?refesh=true,请求照常执行、不刷新;改成 ?refresh=true 之后,每次写入都会刷新。参数是生效了,但可能不是你想要的行为。所以我的建议是改这一类的时候,同时确认这个参数生效后意味着什么,改完观察一下。
多传的、已经废弃的参数是另一类,直接删掉就行,它们在旧版本本来就不生效,删掉不改变结果。
图 3:官方文件 IndexRequest.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 IndexRequest.java
怎么定位被拒的参数
unrecognized parameter 的报错是在 HTTP 响应里返回的。ES 服务端日志里不一定有对应行,所以从自己的调用方日志或网关访问日志里提取更可靠:
grep -o "contains unrecognized parameter: \[[^]]*\]" app.log | sort | uniq -c | sort -rn
拿到参数名之后按实际拼写(包括拼错的那些)回搜代码:
grep -rn "refesh\|ignore_unavailble" --include=*.py --include=*.java . | head -10
结果通常很集中,就那么几个参数名被反复用错。我的判断是命中以后必须人看上下文,正则只能提供线索。
_id 长度限制 512 字节
When specifying an
_idvalue longer than 512 bytes, the request will be rejected.
源码在 IndexRequest 第 192–194 行,校验的是 UTF-8 编码后的字节数,不是字符数:
if (id != null && id.getBytes(StandardCharsets.UTF_8).length > 512) {
validationException = addValidationError("id is too long, must be no longer than 512 bytes but was: " +
id.getBytes(StandardCharsets.UTF_8).length, validationException);
}
用超长字符串(比如把整段内容、超长 URL 当作 ID)做主键的写入,升级 5.0 后会被拒,需要在应用侧改成哈希或截断。非 ASCII 的 id 要注意:中文字符在 UTF-8 下通常是 3 字节,按字符数估算会低估。
/_optimize 删除
The deprecated
/_optimizeendpoint has been removed. The/_forcemergeendpoint should be used in lieu of optimize. TheGETHTTP verb for/_forcemergeis no longer supported, please use thePOSTHTTP verb.
两件事:端点改名为 /_forcemerge;HTTP 方法从 GET 改成 POST。force merge 是有副作用的写操作,用 GET 本来就不合适。运维脚本里的 force merge 调用要一起改这两项。
创建索引只接受 PUT
It used to be possible to create an index by either calling
PUT index_nameorPOST index_name. Only the former is now supported.
原文没有提到这是什么错误类型,也没有说旧行为是否有告警。这条的排查对象主要是脚本和工具里的建索引调用。
类型存在性检查换端点
The endpoint for checking whether a type exists has been changed from
{index}/{type}to{index}/_mapping/{type}in order to prepare for the removal of types whenHEAD {index}/{id}will be used to check whether a document exists in an index. The old endpoint will keep working until 6.0.
这条是给"移除 type"铺路:{index}/{type} 这个 URL 形状将来要让位给 {index}/{id}(检查文档是否存在),所以类型检查挪到 _mapping 下。旧端点保留到 6.0,所以这一项在 5.x 期间不急,但要在 6.0 之前改完。
/_cluster/stats 的两处结构变化
mem 段
The
memsection contained only thetotalvalue, which was actually the memory available throughout all nodes in the cluster. The section contains nowtotal,free,used,used_percentandfree_percent.
原来只有一个 total(集群所有节点可用内存之和),现在拆成 total、free、used、used_percent、free_percent。监控脚本里读这个字段的要改:原来 total 的含义是"可用内存",改名之后同一份脚本如果继续读 total,数值的含义可能已经不一样,建议按新的字段名重新取值。
节点角色统计
The
client,master_only,data_onlyandmaster_datafields have been removed in favor ofmaster,data,ingestandcoordinating_only. A node can contribute to multiple counts as it can have multiple roles. Every node is implicitly a coordinating node, so whenever a node has no explicit roles, it will be counted as coordinating only.
从"互斥的角色分类"改成"可叠加的角色计数"。因为一个节点可以同时有多个角色,所以会被计入多个计数,各计数相加可能超过节点总数,这是预期行为,不是统计错误。
这个变化和 5.0 删除 node.client、要求角色分开配置(node.master、node.data、node.ingest)是配套的:角色从"打包选项"变成"可组合开关",统计口径也跟着变。同一页还有一条相关改动(第 58–63 行):节点角色不再放在节点属性里,改到 nodes stats / nodes info 响应的 roles 段,是个数组;数组为空表示这个节点只做协调。
同一页里另外几条值得先看的
迁移文档这一页还有几条,本文没有逐条展开,升级前建议先看一遍:
- 禁止未加引号的 JSON:字段名不带引号的文档不再被接受。如果 2.x 期间已经写入过这类文档,部分操作可能报错。为了迁移,5.x 的
jvm.options里提供了一个注释掉的参数-Delasticsearch.json.allow_unquoted_field_names,官方说明它只为迁移提供,会在 6.0 移除(第 65–75 行); - Analyze API 参数改名:
filters改filter,char_filters改char_filter,token_filters删除,用filter(第 77–80 行); DELETE /_query删除,改用 Delete By Query API(第 82–85 行);/_cluster/health的wait_for_relocating_shards(数字)改成布尔的wait_for_no_relocating_shards,默认 false(第 104–108 行);- 部分端点不再支持 Java properties 格式的内容(第 99–102 行)。
升级检查清单
- 把带查询串参数的请求过一遍,删掉多余参数;拼错的参数改对之前先确认它生效后意味着什么;
- 检查有没有超长
_id(UTF-8 后超过 512 字节); - 脚本里
/_optimize改成/_forcemerge,方法改 POST; POST index_name建索引改成 PUT;- 类型存在性检查改用
HEAD {index}/_mapping/{type}(6.0 之前完成); - 监控解析
/_cluster/stats的代码改mem段与角色统计字段;节点角色改读roles数组; - 同一页其余条目(未加引号 JSON、Analyze API 参数、
DELETE /_query、wait_for_no_relocating_shards)过一遍。
多写一个参数会怎样
这条改动不用读代码,打一次请求就清楚了:
图 4:本地 5.6.16 单节点实例上的实测输出。上:合法参数照常返回;下:多写一个不认识的参数
curl -s "http://localhost:9200/_search?foo=bar" \
| jq -r '.error.reason'
返回 request [/_search] contains unrecognized parameter: [foo],HTTP 400。
我的建议是把这条当成迁移时的第一道筛子:以前拼错的参数会被静默忽略,5.0 之后直接 400。所以最先出问题的是那些"一直在跑、没人看过返回值"的定时任务和监控脚本,业务查询反而容易在联调时就发现 —— 前者在旧版本里可能已经错了很久。
写在最后
URL 参数从严和设置校验从严是同一件事的两面:一个管请求,一个管配置文件,方向都是不再接受不认识的东西。代价是升级时集中报错,收益是"以为在生效其实没有"的参数被暴露出来。
对使用者的实际影响集中在自己拼 URL 的调用方。用官方客户端的一般不受影响,因为参数由客户端构造。升级后报参数错误时,先确认这是不是从旧文档或旧代码复制过来的历史遗留参数。
排查时不只为了消错,还要看一眼每个被拒的参数是干什么的。如果它本该有用,那等于发现了一个一直没生效的问题:你以为设置了超时、设置了路由,实际上都被忽略了。静默忽略的问题不会自己浮现,只会越积越多。
参考
本文对照 Elasticsearch
v5.6.16标签的官方文档核对,引文逐字取自原文;文中「实测」小节的命令与输出来自本地 5.6.16 单节点实例,不含性能对比。
