考古|ES 5.0 的 Java 客户端要重新依赖,transport client 单独成包
5.0 的 Java API 变更里,最容易被注意到的是三处:transport client 从核心拆成独立 artifact(
org.elasticsearch.client:transport),构造方式换成PreBuiltTransportClient;count API 删除,改用size(0)的 search;suggest API 删除,改用 search 的 suggest 选项。同一页还有一批更琐碎但同样会编译失败的改动:SearchSourceBuilder这类构建器去掉了接收XContentBuilder、Map、bytes[]的重载,写入请求的setWriteConsistencyLevel换成setWaitForActiveShards,DocumentAlreadyExistsException删除,以及 ES 不再自动探测日志实现。本文对照 v5.6.16 逐条列出。
说明
实测环境:本地 Elasticsearch 5.6.16 官方 tar 包(配 .eslab/jdk 下的 JDK 8),逐个用不同 -E 参数拉起独立单节点实例(独立 path.data 与端口 9761–9764),每次等 11 秒看进程是否还在,跑完进程已清理。
来源和边界先交代:本文的依据出自 v5.6.16 标签下的官方文件(java.asciidoc、PreBuiltTransportClient.java、SearchHits.java),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

图 1:官方文件 java.asciidoc(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 java.asciidoc
Transport client 独立成模块
5.0 迁移文档(migrate_5_0/java.asciidoc 第 7–29 行):
The Java transport client has been moved to its own module which can be referenced using:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.0.0</version>
</dependency>
构造方式也变了:
TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));
以前引 elasticsearch 依赖就能用 transport client,5.0 起要单独引 transport。
PreBuiltTransportClient 这个名字的含义可以从源码确认(PreBuiltTransportClient 第 90–97 行):它预先装好一组插件,5.6.16 里是 Netty3Plugin、Netty4Plugin、ReindexPlugin、PercolatorPlugin、MustachePlugin、ParentJoinPlugin。所以"PreBuilt"指的是预装插件,调用方不用自己拼插件列表。它还提供了带额外插件的构造重载(第 106–118 行)。另外它的静态初始化会关掉 Netty 的若干默认行为(第 68–80 行):禁用 unsafe、不替换 selector key set、把 recycler 每线程容量设为 0,这些可以通过系统属性覆盖。

图 2:官方文件 PreBuiltTransportClient.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 PreBuiltTransportClient.java
Count API 删除
The deprecated count api has been removed from the Java api, use the search api instead and set size to 0.
原来:
client.prepareCount(indices).setQuery(query).get;
改成:
client.prepareSearch(indices).setSource(new SearchSourceBuilder.size(0).query(query)).get;
这对应 5.0 里 search_type=count 的移除:count 的语义完全能用 size: 0 的 search 表达,独立 API 没有存在必要。
改完要注意响应解析的变化:count 响应读 getCount,search 响应读 getHits.getTotalHits。v5.6.16 里 SearchHits.totalHits 是 long(SearchHits.java 第 51 行),所以这里的类型对得上。后续大版本把它换成了带 relation 的对象,那是之后的事,本文没有逐版本核对。

图 3:官方文件 SearchHits.java(v5.6.16 标签)。来源:GitHub elastic/elasticsearch v5.6.16 SearchHits.java
Suggest API 删除
The suggest api has been removed from the Java api, use the suggest option in search api, it has been optimized for suggest-only request.
原来:
client.prepareSuggest(indices).addSuggestion("foo", SuggestBuilders.completionSuggestion("field").text("s")).get;
改成:
client.prepareSearch(indices).suggest(new SuggestBuilder.addSuggestion("foo",
SuggestBuilders.completionSuggestion("field").text("s"))).get;
官方说这条路径"针对只有 suggest 的请求做过优化",所以不用担心走 search 会多算命中。
prepareCount、prepareSuggest 在 v5.6.16 的 Client 接口里确实没有了,本文按源码确认过这一点。
同一页里另外几条会编译失败的改动
这几条原文没写,但同样属于这一次迁移的范围(都在同一页):
- 日志实现不再自动探测(第 68–76 行):ES 现在用 Log4j 2,以前 Log4j 不在 classpath 时会降级到 SLF4J 或 Java logging,现在不行——没有 Log4j 2 API 就跑不起来。用 Java 客户端时
log4j-over-slf4j桥接应该可用,仍然依赖 Log4j 1 API 的第三方依赖用log4j-1.2-api桥接; DocumentAlreadyExistsException删除(第 89–93 行):换成VersionConflictEngineException,报错描述更清楚。影响用IndexRequest.opType或IndexRequest做"不存在才写入"的代码;setWriteConsistencyLevel换成setWaitForActiveShards(第 95–113 行):原因是写入一致性语义含糊,它只检查操作开始前有足够分片可用,不保证操作结束时数据已复制到那些副本。新方法接受总数以内的数值或ActiveShardCount.ALL,默认只等主分片。影响IndexRequest、BulkRequest、UpdateRequest、DeleteRequest及各自的 Builder;- 构建器去掉
XContentBuilder/Map/bytes[]重载(第 289–331 行):SearchSourceBuilder、SearchRequestBuilder、SearchRequest、AggregationBuilder都改成只接收对应的 builder 对象(HighlightBuilder、AggregationBuilder、SuggestBuilder等),好处是请求在调用时就能校验,报错更清楚。同一段还删掉了setExtraSource、setTemplateSource、SearchRequest上的source系列方法,模板查询改用SearchTemplateRequest; - Query Builders 一批构造参数变必填(第 115–288 行):
BoostingQueryBuilder的 positive/negative、SpanContainingQueryBuilder的 big/little、SpanNearQueryBuilder的 slop 等改到构造函数里且不能为 null,配套的QueryBuilders静态方法也跟着改。SpanOrQueryBuilder加子句的方法从clause(...)改名为addClause(...); SearchResponse里 string 字段的排序值类型变了(第 321–324 行):从org.elasticsearch.common.text.Text变成java.lang.String。
这一页的条目有几十项,上面列出的是改动面较大的几类。逐条核对请以迁移文档为准。
真正的决策是"还要不要用 transport client"
换 artifact、换构造方式、count 和 suggest 换写法,这些都不难,编译器会逐个列出来。需要想清楚的是另一件事:这个客户端你还打算用多久。
事实是 5.0 把它拆成独立模块,之后它被标记废弃,官方主推的方向变成 REST 客户端。transport client 在 7.0 标记废弃、8.0 移除,这是公开的版本演进事实(本文没有逐版本核对时间线)。所以这次迁移做完,几年后很可能还要再迁一次。
我的建议是:如果改动量不大,直接换成 REST 客户端;如果确实大,至少把 transport client 的调用收敛到单一模块,别散落在业务代码各处。判断的出发点是技术选型要看终点——一个在官方规划里要被取代的组件,继续在上面投入等于给自己排下一次迁移。
迁移步骤
- 构建里加
org.elasticsearch.client:transport,把new TransportClient(...)改成new PreBuiltTransportClient(...); - 全量替换
prepareCount(...),响应解析从getCount改成getHits.getTotalHits; - 全量替换
prepareSuggest(...),改用prepareSearch(...).suggest(...); - 处理上面几类改动:
setWriteConsistencyLevel换setWaitForActiveShards、DocumentAlreadyExistsException的 catch、构建器里传XContentBuilder/Map/bytes[]的调用、Query Builders 的构造参数; - 确认 classpath 里有 Log4j 2 API,必要时配桥接;
- 确认客户端版本与集群版本一致;
- 项目里如果还留着内嵌节点那套写法(
node.client),节点角色已经改成node.master/node.data/node.ingest分开配置,一并处理。
怎么找改动点
# transport client 构造(旧写法)
grep -rn "new TransportClient(" --include=*.java . | head -20
# count / suggest 的 Java API 调用
grep -rn "prepareCount\|prepareSuggest" --include=*.java . | head -20
# 写入请求的一致性设置
grep -rn "setWriteConsistencyLevel" --include=*.java . | head -20
# 依赖里的 client artifact
grep -rn "org.elasticsearch.client" --include=pom.xml --include=*.gradle . | head -20
前三条是文本匹配,命中后要人看上下文。验证方式很直接:编译一遍。这些都是编译期可见的改动(类没了、方法签名变了、构造参数不够),编译器会把漏改的地方列出来。改完跑一遍集成测试,确认总数和以前一致。
版本对齐
transport client 的实际约束是客户端与集群版本要对齐,集群升了客户端就要跟着升,否则可能连不上或行为异常。官方对"必须完全一致"的表述本文没有查到确切出处,落地时以你所用版本的 Java 客户端文档为准。
这也是我建议评估 REST 客户端的原因之一:通过 HTTP 访问对版本的要求宽松得多,升级时不用被迫同步发版。
如果暂时还得用 transport client,可以做的几件事:
- 把客户端版本做成依赖管理的单一来源,别散落在各个服务的 pom / gradle 里;
- 升级集群前先列出所有客户端,确认能一起升;
- 在 CI 里加一条版本一致性检查,避免某个服务用了旧版本。
这几条能挡住"集群升完了、某个服务连不上"这类问题。
多服务一起升级时,先升一个非关键服务观察一段时间(连得通、查询正常、没有序列化异常),再批量铺开。旧版本的客户端 jar 保留一段时间:transport client 与集群版本强绑定,集群升完而客户端没跟上时,回滚往往只能把客户端版本降回去,升级前确认旧版本还能拿到。
在 5.6.16 上跑一遍
Java API 那几条要编译源码才能验,但正文末尾提到的节点角色设置可以放到启动期直接试。

图 4:5.6.16 带 -Enode.client=true 启动,报 unknown setting [node.client] please check that any required plugins are installed, or check the breaking changes documentation for removed settings。本地实测输出。

图 5:同一个 5.6.16 上,-Enode.master=false、-Enode.data=false、-Enode.ingest=true 三个设置都能让节点正常启动。本地实测输出。
JAVA_HOME=<jdk8> ES_JAVA_OPTS='-Xms512m -Xmx512m' ./bin/elasticsearch \
-Ediscovery.type=single-node -Ehttp.port=9761 -Enode.client=true ...
# unknown setting [node.client] ...
curl -s '' | jq -r '.nodes[].roles | join(",")'
# 新角色设置下节点正常返回角色列表
一个是「未知设置、启动即失败」,另外三个是「收下并照常启动」。这跟正文里那句「节点角色已经改成 node.master / node.data / node.ingest 分开配置」对得上:旧的一体化客户端节点开关在 5.x 已经不存在,想配成不存数据、只做协调的节点,得用三个布尔设置组合出来。
结论是升级脚本里但凡还留着 node.client,节点连启动这一关都过不去,属于最容易发现的一类;真正容易漏的是依赖 transport client 的 Java 代码——编译期就报错,但没有运行期信号。我的建议是先把部署配置里的节点角色设置全量 grep 一遍,再处理 Java 依赖,两处一起改完再启动。
边界:
- 图 4、图 5 出自 5.6.16 的启动期检查,只判断进程是否存活,没有读取节点的实际角色列表或验证请求路由;
- 本文其余条目(
PreBuiltTransportClient、count/suggest API 删除、构建器重载、setWaitForActiveShards、日志实现探测)是 Java 编译期改动,本地没有搭 Java 工程,均未实测; - 5.0 之前没有对应版本二进制,
node.client曾经生效的形态未做运行期对照。
写在最后
这次 Java API 的改动分成几层:transport client 独立成模块、count 和 suggest 两个入口删除、一批构建器和请求对象的方法签名变化、日志实现和异常类型的调整。前三层编译期可见,改起来快。
改动本身不是难点。值得借这次机会评估的是这个客户端还要不要用——它在此后的版本里被标记废弃并移除,继续投入等于给几年后再排一次迁移。如果暂时不换,至少把调用收敛到一处、把版本做成单一来源,这两步成本不高。
参考
本文对照 Elasticsearch
v5.6.16标签的官方文档核对,引文逐字取自原文。「在 5.6.16 上跑一遍」里已实际用启动参数验证节点角色设置:node.client=true报unknown setting [node.client]并启动失败,node.master=false/node.data=false/node.ingest=true三个都正常启动(见该节)。Java 客户端依赖、count/suggest API 删除、构建器重载等编译期改动需要 Java 工程,本机未搭建、未实测;5.0 之前的形态未做运行期对照。
- 点赞
- 收藏
- 关注作者
评论(0)