压测时间歇性出现javax.ws.rs.ProcessingException: Already connected错误求助
我之前处理过几乎一模一样的问题,这个错误的根源其实是Jersey Client的对象线程安全问题——准确来说,你大概率是在多线程环境下复用了非线程安全的请求构建对象。
问题原因拆解
Jersey的Client实例本身是线程安全的,可以全局复用,但以下对象都是非线程安全的,绝对不能在多线程间共享:
WebTargetInvocation.Builder- 基于上述对象创建的
ClientRequest/Invocation实例
在高并发场景下,如果多个线程共享同一个Builder或WebTarget,就会出现多个线程同时修改请求头、写入请求体的情况,导致底层的HttpURLConnection在已经建立连接后,还被尝试设置请求属性,最终抛出Already connected异常。从你的堆栈也能看到,异常是在setRequestProperty时触发的,完全符合这个场景。
具体解决方案
1. 确保每个请求都创建独立的请求构建对象
把线程安全的Client作为全局实例复用,每次发起请求时都从Client重新创建WebTarget和Builder,示例代码如下:
// 全局复用的Client(线程安全) private static final Client JERSEY_CLIENT = JerseyClientBuilder.withConfig(config).build(); public void callRestApi() { // 每次请求都创建独立的WebTarget WebTarget target = JERSEY_CLIENT.target("https://your-api-domain.com/path"); // 创建独立的Invocation.Builder Invocation.Builder requestBuilder = target.request(MediaType.APPLICATION_JSON); // 设置请求头、请求体(这里的操作都是线程安全的,因为Builder是当前线程独享) requestBuilder.header("Authorization", "Bearer token"); Response response = requestBuilder.put(Entity.json(yourRequestBody)); // 处理响应... }
❌ 绝对不要做的事:把WebTarget或Invocation.Builder作为类的静态/实例变量缓存,让多个线程共享。
2. 排查工具类中的共享对象
从你的堆栈信息来看,异常出现在ToolkitUtil.selectSelfServiceProfile方法的put调用上,建议检查这个方法里是否复用了非线程安全的请求对象。比如有没有把Builder或者WebTarget做成了静态变量,导致多线程同时操作同一个实例。
3. 切换到底层连接器(高并发场景推荐)
默认的HttpUrlConnector在高并发下表现一般,推荐切换到Apache HttpClient作为底层连接器,它的连接池管理和线程安全性更成熟:
ClientConfig clientConfig = new ClientConfig(); // 配置连接超时、读取超时 clientConfig.property(ClientProperties.CONNECT_TIMEOUT, 5000); clientConfig.property(ClientProperties.READ_TIMEOUT, 10000); // 设置Apache连接器 clientConfig.connectorProvider(new ApacheConnectorProvider()); Client jerseyClient = JerseyClientBuilder.withConfig(clientConfig).build();
这个调整不仅能解决当前的并发问题,还能提升整体的请求性能。
4. 避免复用请求实例
如果你封装了请求逻辑,确保没有把ClientRequest或者Invocation对象缓存起来供多个线程使用,这些对象都是单次请求专用的,用完就应该回收。
验证效果
按照上述方法修改后,再进行50+用户的压测,应该就不会再出现Already connected的间歇性异常了。我当时就是因为在工具类里错误地把WebTarget做成了静态变量,导致多线程共享冲突,改完每次请求创建独立的构建对象就彻底解决了问题。
内容的提问来源于stack exchange,提问作者user838634

