You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

是否应复用javax.ws.rs.client.Client实例?负载测试与问题分析

关于javax.ws.rs.client.Client复用的疑问与测试分析

API 的主要入口是 ClientBuilder,用于创建 Client 实例——这些实例是可配置的重量级对象,负责管理底层通信基础设施,是访问任何 Web 资源的根对象。

我不确定这段描述指的是Client还是ClientBuilder实例。经网络搜索,似乎是指Client,但……

我开展了一项简单的负载测试:启动10个线程,每个线程执行50次调用,调用间隔为100-500ms的随机值。
每次测试调用实际包含2个HTTP请求:

  • 使用javax.ws.rs.client.Client手动请求获取访问令牌;
  • MicroProfile REST Client 管理的最终请求,通过@ClientHeaderParam(name = HttpHeaders.AUTHORIZATION, ...)注入上述访问令牌。

测试环境为WildFly 26实例。

第一种实现——每次调用执行ClientBuilder.newClient()

注:每次使用后关闭Response和Client。

  • 最小值:31 ms
  • 最大值:196 ms
  • 平均值:约48 ms

第二种实现——共享ClientBuilder实例

注:每次使用后关闭Response和Client。

  • 最小值:25 ms
  • 最大值:72 ms
  • 平均值:约32 ms

此方案性能有所提升,可见共享ClientBuilder确实有益。两种实现均运行正常。

第三种实现——共享Client实例

注:每次使用后关闭Response。
该测试无法完整执行,每个线程执行到第7-10次迭代时开始抛出错误,因此结果有限:

  • 最小值:14 ms
  • 最大值:53 ms
  • 平均值:约21 ms

共享实例看似是最优选择,但错误导致此方案不可行。
错误信息:java.net.SocketException: Software caused connection abort: recv failed

第四种实现——使用commons-pool2实现共享池化Client实例

注:每次使用后关闭Response,并将Client归还至池。
创建了一个最大容量为20的通用池,Client工厂使用共享的ClientBuilder,运行时按需创建10个Client(对应负载测试的10个线程)。但该测试同样在每个线程执行到第9-12次迭代时抛出相同错误,结果也有限:

  • 最小值:15 ms
  • 最大值:67 ms(存在约5500 ms的异常值)
  • 平均值:约20 ms(忽略异常值)

可见使用池化共享Client实例并无收益。

观察结果

表面上Client实例应被复用,但实际使用多次后会出现故障。
初步检查MicroProfile REST Client发现,它似乎总是使用新的ClientBuilder,但这是不同的实现,无法深入研究。

我的问题

我是否应该复用javax.ws.rs.client.Client实例?

用例说明

我有一个MicroProfile REST Client连接至认证端点,采用OpenID Connect进行认证。
使用@ClientHeaderParam(name = HttpHeaders.AUTHORIZATION, ...)将认证头注入MP客户端请求,该注解指向一个认证头生成器,通过与OpenID服务器(KeyCloak)进行client_credential认证获取有效令牌,并返回给MP方法调用。


编辑1

我启用了部分日志,发现大量如下调试输出:

[org.apache.http.impl.conn.PoolingHttpClientConnectionManager] (Thread-*) Connection request: [route: {}->http://localhost:80][total available: 0; route allocated: 50 of 50; total allocated: 50 of 50]

该日志代表对REST端点的请求,而非OpenID服务器。

现在我怀疑问题并非直接出在Client实例,而是MicroProfile REST Client。当共享Client实例时,MP连接池释放租用连接的速度过慢,导致等待可用连接时出现阻塞;而不共享Client实例时,连接释放更快,从未出现allocated: 50 of 50的情况。

基于此发现,我将调用隔离至Callable中,解决了错误问题,但平均耗时却飙升至荒谬的2秒!

我现在有一个新问题:Client实例共享与MicroProfile REST Client连接池有何关联?


编辑2

无论如何构建REST客户端,最终都会使用org.apache.http.impl.conn.PoolingHttpClientConnectionManager。

当使用@Inject @RestClient时,连接无法复用,释放速度极慢,很快就达到50/50的分配上限,进而开始锁定资源。

当使用RestClientBuilder.newBuilder().baseUri(uri).build(Client.class)手动构建代理时,始终使用全新的连接管理器(分配1/50),且连接管理器会在每次连接使用后立即关闭。

我认为MicroProfile REST Client构建器会共享构建器,它尝试共享客户端,但对于同一端点的每次请求(令牌过期时请求头会变化),无法复用池中的客户端连接。


内容的提问来源于stack exchange,提问作者Claudio Weiler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 21:02:11