HttpClient高并发内存溢出

apache 的HttpClient很强大,据说可以承受一万左右的高并发,但是在做项目的时候用HttpClient进行附件上传,并发1000不到的时候都导致了内存溢出,核心代码为:

HttpPost post = new HttpPost(url.toString()); post.setEntity(multipartEntityBuilder.build());

HttpResponse response = httpClient.execute(post);

经过调试和查看httpClient的源码发现,由于httpClient.execute(post)返回值response并不能close,因此在上传大文件而且高并发的情况下会导致线程一直被占用,导致资源不足

用CloseableHttpClient来替代HttpClient,httpClient.execute(post)得到CloseableHttpResponse可以被关闭,从而避免了上述问题

  HttpPost post = new HttpPost(url.toString());
        post.setEntity(multipartEntityBuilder.build());
        // try with resource语法response.close()会被自动调用
        try (CloseableHttpResponse response = httpClient.execute(post)) { 

        } catch (Exception ex) {

        } finally {

        }
### Feign Client 大文件上传内存溢出解决方案 当使用 `@FeignClient` 进行大文件上传时,可能会遇到内存溢出的问题。这是因为默认情况下,Feign 客户端会将整个请求体加载到内存中再发送给服务器。对于较大的文件,这种方式可能导致内存不足。 以下是针对该问题的几种常见优化措施: #### 1. 调整 HttpClient 的连接池参数 通过调整 HTTP 请求的最大连接数和每路由最大连接数来提高性能并减少内存占用。可以设置如下属性: ```properties spring.cloud.openfeign.httpclient.max-connections=200 spring.cloud.openfeign.httpclient.max-connections-per-route=50 ``` 这些配置项能够有效控制并发量,从而降低因高并发引起的内存压力[^1]。 #### 2. 修改 Feign 编码器支持流式传输 默认情况下,Spring Cloud OpenFeign 使用的是 `FormEncoder` 或其他编码器,它们通常会在内存中构建完整的 multipart/form-data 数据包后再发起网络请求。为了处理大文件,建议自定义编码器以启用流式传输机制。 可以通过实现 `RequestInterceptor` 来拦截请求并对数据进行分片读取操作。例如: ```java @Bean public Encoder feignMultipartEncoder() { return new SpringFormEncoder(); } ``` 如果需要更高级别的定制化功能,则可考虑引入 Apache Commons FileUpload 库或者 OkHttp 的 MultipartBody.Builder 工具类完成类似逻辑改造工作。 #### 3. 增加 JVM 参数分配更多堆外内存 适当增加 Java 虚拟机启动选项中的 `-Xmx` 和 `-XX:MaxDirectMemorySize` 数值大小也可以缓解部分场景下发生的 OOM 错误现象。比如下面这个例子就是把最大可用直接缓冲区容量设成了 4GB: ```bash -javaagent:/path/to/jolokia.jar -server -Xms512m -Xmx4g -XX:+UseG1GC -XX:MaxDirectMemorySize=4g ``` #### 4. 利用 NIO 提升 I/O 效率 采用非阻塞式的异步编程模型(Asynchronous Programming Model),充分利用现代操作系统所提供的高性能输入输出能力——即所谓的 New Input Output (NIO),进一步提升系统的吞吐能力和稳定性表现。 具体做法是在项目依赖里加入 netty-all 版本号不低于 4.x 的 Maven POM 文件片段;然后基于 Netty 框架重新设计一套专门用于管理二进制大数据块收发流程的服务组件架构图样例代码展示如下所示: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.68.Final</version> </dependency> ``` 最后记得测试验证改动后的效果如何! --- ### 示例代码:自定义 Feign 编码器 以下是一个简单的示例,展示了如何创建一个支持流式传输的大文件上传编码器: ```java import org.springframework.cloud.openfeign.support.SpringEncoder; import feign.RequestTemplate; @Component public class CustomMultipartEncoder extends SpringEncoder { private final ObjectFactory<HttpMessageConverters> messageConverters; public CustomMultipartEncoder(ObjectProvider<HttpMessageConverters> provider) { super(provider); this.messageConverters = () -> provider.getObject(); } @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws IOException { if (object instanceof MultiValueMap<?, ?> && Multipart.class.isAssignableFrom(bodyType)) { try { HttpHeaders headers = new HttpHeaders(); MediaType contentType = MediaType.MULTIPART_FORM_DATA; headers.setContentType(contentType); HttpOutputMessage outputMessage = new StreamedMultipartRequestBody(template.request().headers()); ((MultiValueMap<String, ?>) object).forEach((key, values) -> values.forEach(value -> addPart(key, value, headers, outputMessage))); writeHeaders(outputMessage.getHeaders(), template); } catch (Exception ex) { throw new RuntimeException(ex.getMessage(), ex); } } else { super.encode(object, bodyType, template); } } private static void addPart(String key, Object partContent, HttpHeaders httpHeaders, HttpOutputMessage outputMessage) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); if (partContent instanceof Resource || partContent instanceof InputStreamSource) { // Handle file/resource content as stream. byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = ((InputStreamResource) partContent).getInputStream().read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } } else { byteArrayOutputStream.write(partContent.toString().getBytes(StandardCharsets.UTF_8)); } ContentDisposition disposition = ContentDisposition.builder("form-data").name(key).build(); httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION, disposition.toString()); outputStream.writeTo(byteArrayOutputStream.toByteArray()); } } class StreamedMultipartRequestBody implements HttpOutputMessage { Map<String,List<?>> parts=new HashMap<>(); List<Map.Entry<String,String>> headerFields=new ArrayList<>(); public StreamedMultipartRequestBody(Map<String,?> rawHeaders){ parseRawHeader(rawHeaders); } private void parseRawHeader(Map<String,?> rawHeaders){ for(var entry :rawHeaders.entrySet()){ var k=(String )entry.getKey(); var v=(List<?> )entry.getValue(); if(v!=null&& !v.isEmpty())headerFields.add(new AbstractMap.SimpleEntry<>(k,(String)v.iterator().next())); } } @Override public OutputStream getBody(){ return new SequenceInputStream(parts.values().stream() .map(it->{ StringBuilder sb=new StringBuilder("--boundary\r\n"); it.forEach(item->{if(item instanceOf String ){ sb.append("Content-Disposition: form-data; name=\""+item+"\"\r\n\r\n"+item+"\r\n"); }else{ /* handle resource */}}); return new ByteArrayInputStream(sb.toString().getBytes());}) .collect(Collectors.toList()).iterator()); } @Override public HttpHeaders getHeaders(){return null;} } ``` 上述代码实现了对多部件表单数据的支持,并允许逐块写入资源内容至输出流中,而不是一次性将其全部载入内存。 --- ### 总结 综上所述,解决 Feign 客户端在上传大文件过程中发生 Out Of Memory Error 的主要策略包括但不限于以下几个方面:合理调节底层 HTTP 客户端库的相关参数设定、改写现有的消息序列化工厂实例以便适应特定需求以及借助外部工具链增强整体框架运行效率等等。实际应用当中还需要结合具体情况灵活运用各种技术手段综合施策才能达到最佳成效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值