Spring Boot是一个非常流行的 Java 企业应用程序框架。与内部或外部应用程序集成的一种常见方法是通过 HTTP REST 连接。我们从RestTemplate升级到基于 Java NIO 的WebClient,它可以通过在调用 REST 服务端点时允许并发来显着提高应用程序性能。WebClient 的好处如下:
- 并发性: WebClient 能够同时处理多个连接,而不会阻塞线程,从而实现更好的并发性。
- 异步:异步编程允许应用程序在等待 I/O 操作完成的同时执行其他任务,从而提高整体效率。
- 性能:非阻塞 I/O 可以用更少的线程管理更多的连接,从而减少处理并发请求所需的资源。
尽管性能有所提高,但在并发连接数相同的情况下,WebClient 会崩溃OutOfMemoryError。我们将分析 WebClient 崩溃问题以及如何排除故障和修复它们。为了利用 NIO 的优势(如并发和异步处理),我们将其余客户端调用从 Spring RestTemplate 升级到了 WebClient,如下所示。
Spring RestTemplate
public void restClientCall(Integer id, String url,String imagePath) {
// Create RestTemplate instance RestTemplate restTemplate = new RestTemplate();
// Prepare the image file File imageFile = new File(imagePath);
// Prepare headers HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// Prepare the request body MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("file", new org.springframework.core.io.FileSystemResource(imageFile));
// 创建带有标题和多部分主体的 HTTP 实体 HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
System.out.println("Starting to post an image for Id"+id);
// Perform the POST request ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
// Print the response status code and body System.out.println("Response Id "+id +":"+ responseEntity.getBody()); System.out.println(" Time: " + LocalTime.now()); }
|
下面是 Spring WebClient:
public void webHeavyClientCall(Integer id,String url, String imagePath) {
// Create a WebClient instance WebClient webClient = WebClient.create();
// Prepare the image file File imageFile = new File(imagePath);
//执行 POST 请求,将图片作为请求正文的一部分 MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("file", new FileSystemResource(imageFile)); System.out.println("Image upload started "+id); webClient.post().uri(url).contentType(MediaType.MULTIPART_FORM_DATA).body(BodyInserters.fromMultipartData (body)).retrieve().bodyToMono(String.class).subscribe(response -> { System.out.println("Response Id"+id+ ":" + response); });
|
WebClient 导致内存不足错误
当我们在 OpenJDK 11 中运行这两个程序时。使用基于 NIO 的 Spring WebClient 的程序在迭代几次后出现了 "java.lang.OutOfMemoryError:Direct buffer memory",而基于 Spring RestTemplate 的程序则成功完成。
下面是基于 NIO 的 Spring WebClient 程序的输出结果。您可以看到报告了 "java.lang.OutOfMemoryError"(java.lang.OutOfMemoryError)。
Starting to post an image for Id0
Starting to post an image for Id1
Starting to post an image for Id2
Starting to post an image for Id3
Starting to post an image for Id4
Starting to post an image for Id5
Starting to post an image for Id6
Starting to post an image for Id7
Starting to post an image for Id8
Starting to post an image for Id9
Starting to post an image for Id10
Starting to post an image for Id11
Starting to post an image for Id12
Starting to post an image for Id13
Starting to post an image for Id14
2023-12-06 17:21:46.730 WARN 13804 --- [tor-http-nio-12] io.netty.util.concurrent.DefaultPromise : An exception was thrown by reactor.ipc.netty.FutureMono$FutureSubscription.operationComplete()
reactor.core.Exceptions$ErrorCallbackNotImplemented: io.netty.channel.socket.ChannelOutputShutdownException: Channel output shutdown
Caused by: java.lang.OutOfMemoryError: Direct buffer memory
at java.base/java.nio.Bits.reserveMemory(Bits.java:175) ~[na:na]
at java.base/java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:118) ~[na:na]
at java.base/java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:318) ~[na:na]
at java.base/sun.nio.ch.Util.getTemporaryDirectBuffer(Util.java:242) ~[na:na]
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:164) ~[na:na]
at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:130) ~[na:na]
at java.base/sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:496) ~[na:na]
at io.netty.channel.socket.nio.NioSocketChannel.doWrite(NioSocketChannel.java:418) ~[netty- transport-4.1.23.Final.jar!/:4.1.23.Final]
at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:934) ~[netty- transport-4.1.23.Final.jar!/:4.1.23.Final]
... 18 common frames omitted
|
Spring WebClient 是基于 Java NIO 技术开发的。在 Java NIO 中,对象存储在 JVM 本机内存的 "直接缓冲存储器 "区域,而 RestTemplate 对象存储在 JVM 本机内存的 "其他 "区域。JVM 中有不同的内存区域。要了解它们,请观看本视频短片。
执行上述两个程序时,我们将直接缓冲区内存大小设置为 200k(即 -XX:MaxDirectMemorySize=200k)。这个大小对 Spring RestTemplate 来说足够了,因为对象从未存储在这个区域中,但对 Spring WebClient 来说却不够。因此,Spring WebClient 出现了 java.lang.OutOfMemoryError:直接缓冲内存。
增加 -XX:MaxDirectMemorySize
发现这个问题后,我们使用 JVM 参数 -XX:MaxDirectMemorySize=1000k 将直接内存大小增加到更高值。做出这一更改后,Spring WebClient 程序运行得非常好,没有出现任何问题。
结论
在本篇博文中,我们讨论了从 Spring RestTemplate 升级到基于 Java NIO 的 WebClient 时遇到的 OutOfMemoryError 问题。