Java 使用 Spring REST 模板,要么创建过多的连接要么很慢

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31869193/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:42:37  来源:igfitidea点击:

Using Spring REST template, either creating too many connections or slow

javaspringresthttpapache-httpcomponents

提问by MartinTeeVarga

I have a RESTful service that works very fast. I am testing it on localhost. The client is using Spring REST template. I started by using a naive approach:

我有一个运行速度非常快的 RESTful 服务。我正在本地主机上测试它。客户端正在使用 Spring REST 模板。我开始使用一种幼稚的方法:

RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));

Result result = restTemplate.postForObject(url, payload, Result.class);

When I make a lot of these requests, I am getting the following exception:

当我提出很多这些请求时,我收到以下异常:

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/myservice":No buffer space available (maximum connections reached?): connect; nested exception is java.net.SocketException: No buffer space available (maximum connections reached?): connect

This is caused by connections not being closed and hanging in TIME_WAIT state. The exception starts happening when the ephemeral ports are exhausted. Then the execution waits for the ports to be free again. I am seeing peak performance with long breaks. The rate I am getting is almost what I need, but of course, these TIME_WAIT connections are not good. Tested both on Linux (Ubuntu 14) and Windows (7), similar results at different times due to different ranges of the ports.

这是由连接未关闭并挂在 TIME_WAIT 状态引起的。当临时端口耗尽时,异常开始发生。然后执行等待端口再次空闲。我看到长时间休息的最佳性能。我得到的速率几乎是我需要的,但是当然,这些 TIME_WAIT 连接并不好。在 Linux (Ubuntu 14) 和 Windows (7) 上进行了测试,由于端口范围不同,不同时间的结果相似。

To fix this, I tried using an HttpClient with HttpClientBuilder from Apache Http Components library.

为了解决这个问题,我尝试将 HttpClient 与 Apache Http Components 库中的 HttpClientBuilder 一起使用。

RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));
HttpClient httpClient = HttpClientBuilder.create()
        .setMaxConnTotal(TOTAL)
        .setMaxConnPerRoute(PER_ROUTE)
        .build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));

Result result = restTemplate.postForObject(url, payload, Result.class);

With this client, I see no exceptions. The client is now using only a very limited number of ephemeral ports. But whatever settings I use (TOTAL and PER_ROUTE), I can't get the performance I need.

对于这个客户,我没有看到任何例外。客户端现在只使用非常有限数量的临时端口。但是无论我使用什么设置(TOTAL 和 PER_ROUTE),我都无法获得所需的性能。

Using the netstatcommand, I see that there are not many connections done to the server. I tried setting the numbers to several thousands, but it seems the client never uses that much.

使用该netstat命令,我看到与服务器建立的连接并不多。我尝试将数字设置为数千,但客户似乎从未使用过那么多。

Is there anything I can do to improve the performance, without opening too many connections?

在不打开太多连接的情况下,我可以做些什么来提高性能?



UPDATE: I've tried setting number of total and per route connections to 5000 and 2500 but it still looks like the client is not creating more than a hundred (judging from netstat -n | wc -l). The REST service is implemented using JAX-RS and running on Jetty.

更新:我已经尝试将总连接数和每条路由连接数设置为 5000 和 2500,但看起来客户端仍然没有创建超过 100 个(从 判断netstat -n | wc -l)。REST 服务使用 JAX-RS 实现并在 Jetty 上运行。

UPDATE2: I have now tuned the server with some memory settings and I am getting really good throughput. The naive approach is still a bit faster, but I think it's just a little overhead of the pooling on client side.

UPDATE2:我现在已经用一些内存设置调整了服务器,我得到了非常好的吞吐量。天真的方法仍然要快一些,但我认为这只是客户端池化的一点开销。

采纳答案by Colin M

Actually Spring Boot is not leaking connections. What you're seeing here is standard behavior of the Linux kernel (and every major OS). All sockets that are closed from the machine go to a TIME_WAITstate for some duration of time. This is to prevent the next socket that uses that ephemeral port from receiving packets that were actually intended for the previous socket on that port. The difference you're seeing between the two is a result of the connection pooling approaches each one takes.

实际上 Spring Boot 没有泄漏连接。您在这里看到的是 Linux 内核(以及每个主要操作系统)的标准行为。从机器关闭的所有套接字都会在TIME_WAIT一段时间内进入某种状态。这是为了防止使用该临时端口的下一个套接字接收实际用于该端口上的前一个套接字的数据包。您在两者之间看到的差异是每种方法所采用的连接池方法的结果。

More specifically, RestTemplatedoes notuse connection pooling by default. This means every rest call opens a new local ephemeral port and a new connection to the server. If your service is very fast, it will blow through its available local port range in no time at all. With the Apache HttpClient, you are taking advantage of connection pooling. This will prevent your application from seeing the problem that you described. However, given that your service is able to respond faster than the Linux kernel is taking sockets out of TIME_WAIT, connection pooling will make your client slower no matter what you do (if it didn't slow anything down - then you'd run out of local ephemeral ports again).

更具体地说,默认情况下RestTemplate使用连接池。这意味着每次休息调用都会打开一个新的本地临时端口和一个到服务器的新连接。如果您的服务速度非常快,它将很快突破其可用的本地端口范围。使用 Apache HttpClient,您可以利用连接池。这将阻止您的应用程序看到您描述的问题。但是,鉴于您的服务能够比 Linux 内核从 中取出套接字更快地响应TIME_WAIT,无论您做什么,连接池都会使您的客户端变慢(如果它没有减慢任何速度 - 那么您将用完本地临时端口再次)。

While it's possible to enable TCP reuse in the Linux kernel, it's can get dangerous (packets can get delayed and you could get ephemeral ports receiving random packets they don't understand which could cause all kinds of problems). The solution here is to use connection pooling as you have in the second example, with sufficiently high numbers to achieve close to the performance you're looking for.

虽然可以在 Linux 内核中启用 TCP 重用,但它可能会变得危险(数据包可能会延迟,您可能会收到临时端口接收他们不理解的随机数据包,这可能会导致各种问题)。这里的解决方案是使用第二个示例中的连接池,使用足够高的数字来实现接近您正在寻找的性能。

To help you tune your connection pool, you'll want to tweak the maxConnPerRouteand maxConnTotalparameters. maxConnPerRoutelimits the number of connections that will be made to a single IP:Port pair, and maxTotallimits the number of total connections that will ever be opened. In your case, since it appears all requests are made to the same location, you could set them to the same (high) value.

为了帮助您调整连接池,您需要调整maxConnPerRoutemaxConnTotal参数。maxConnPerRoute限制将建立到单个 IP:Port 对maxTotal的连接数,并限制将打开的总连接数。在您的情况下,由于似乎所有请求都是对同一位置发出的,您可以将它们设置为相同的(高)值。