使用 Spring REST 模板,创建的连接过多或速度慢
Using Spring REST template, either creating too many connections or slow
我有一个运行速度非常快的 RESTful 服务。我正在本地主机上测试它。客户端正在使用 Spring REST 模板。我从使用一种天真的方法开始:
RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));
Result result = restTemplate.postForObject(url, payload, Result.class);
当我发出大量此类请求时,出现以下异常:
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
这是由于连接未关闭并挂在 TIME_WAIT 状态造成的。当临时端口耗尽时,异常开始发生。然后执行等待端口再次空闲。我看到长时间休息的最佳表现。我得到的速率几乎是我需要的,但是当然,这些 TIME_WAIT 连接不好。在 Linux (Ubuntu 14) 和 Windows (7) 上进行了测试,由于端口范围不同,在不同时间的结果相似。
为了解决这个问题,我尝试将 HttpClient 与 Apache Http 组件库中的 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);
对于这个客户,我看不出有什么例外。客户端现在只使用非常有限的临时端口。但是无论我使用什么设置(TOTAL 和 PER_ROUTE),我都无法获得所需的性能。
使用 netstat
命令,我发现与服务器的连接并不多。我试着把数字设置成几千,但客户好像从来没有用过那么多。
在不打开太多连接的情况下,我可以做些什么来提高性能?
更新:我已经尝试将总连接数和每个路由连接数设置为 5000 和 2500,但看起来客户端创建的连接数仍然不超过一百(从 netstat -n | wc -l
判断)。 REST 服务是在 Jetty 上使用 JAX-RS 和 运行 实现的。
更新 2:我现在已经用一些内存设置调整了服务器,并且我获得了非常好的吞吐量。天真的方法仍然快一点,但我认为这只是客户端池的一点开销。
实际上Spring 引导不会泄漏连接。您在这里看到的是 Linux 内核(以及每个主要的 OS)的标准行为。从机器上关闭的所有套接字都会在一段时间内进入 TIME_WAIT
状态。这是为了防止使用该临时端口的下一个套接字接收实际上用于该端口上的前一个套接字的数据包。您在两者之间看到的差异是每个连接池方法所采用的结果。
更具体地说,RestTemplate
默认情况下不使用连接池。这意味着每个 rest 调用都会打开一个新的本地临时端口和一个到服务器的新连接。如果您的服务速度非常快,它会立即突破其可用的本地端口范围。使用 Apache HttpClient
,您可以利用连接池。这将阻止您的应用程序看到您描述的问题。但是,鉴于您的服务能够比 Linux 内核从 TIME_WAIT
中取出套接字更快地响应,无论您做什么,连接池都会使您的客户端变慢(如果它没有减慢任何东西) down - 然后你会 运行 再次离开本地临时端口)。
虽然可以在 Linux 内核中启用 TCP 重用,但它可能会变得危险(数据包可能会延迟,您可能会让临时端口接收到它们不理解的随机数据包,这可能会导致各种问题)。这里的解决方案是像第二个示例中那样使用连接池,使用足够高的数字来实现接近您正在寻找的性能。
为了帮助您调整连接池,您需要调整 maxConnPerRoute
和 maxConnTotal
参数。 maxConnPerRoute
限制单个 IP:Port 对的连接数,maxTotal
限制将打开的总连接数。在您的情况下,由于所有请求似乎都是针对同一位置发出的,因此您可以将它们设置为相同的(高)值。
我有一个运行速度非常快的 RESTful 服务。我正在本地主机上测试它。客户端正在使用 Spring REST 模板。我从使用一种天真的方法开始:
RestTemplate restTemplate = new RestTemplate(Collections.singletonList(new GsonHttpMessageConverter()));
Result result = restTemplate.postForObject(url, payload, Result.class);
当我发出大量此类请求时,出现以下异常:
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
这是由于连接未关闭并挂在 TIME_WAIT 状态造成的。当临时端口耗尽时,异常开始发生。然后执行等待端口再次空闲。我看到长时间休息的最佳表现。我得到的速率几乎是我需要的,但是当然,这些 TIME_WAIT 连接不好。在 Linux (Ubuntu 14) 和 Windows (7) 上进行了测试,由于端口范围不同,在不同时间的结果相似。
为了解决这个问题,我尝试将 HttpClient 与 Apache Http 组件库中的 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);
对于这个客户,我看不出有什么例外。客户端现在只使用非常有限的临时端口。但是无论我使用什么设置(TOTAL 和 PER_ROUTE),我都无法获得所需的性能。
使用 netstat
命令,我发现与服务器的连接并不多。我试着把数字设置成几千,但客户好像从来没有用过那么多。
在不打开太多连接的情况下,我可以做些什么来提高性能?
更新:我已经尝试将总连接数和每个路由连接数设置为 5000 和 2500,但看起来客户端创建的连接数仍然不超过一百(从 netstat -n | wc -l
判断)。 REST 服务是在 Jetty 上使用 JAX-RS 和 运行 实现的。
更新 2:我现在已经用一些内存设置调整了服务器,并且我获得了非常好的吞吐量。天真的方法仍然快一点,但我认为这只是客户端池的一点开销。
实际上Spring 引导不会泄漏连接。您在这里看到的是 Linux 内核(以及每个主要的 OS)的标准行为。从机器上关闭的所有套接字都会在一段时间内进入 TIME_WAIT
状态。这是为了防止使用该临时端口的下一个套接字接收实际上用于该端口上的前一个套接字的数据包。您在两者之间看到的差异是每个连接池方法所采用的结果。
更具体地说,RestTemplate
默认情况下不使用连接池。这意味着每个 rest 调用都会打开一个新的本地临时端口和一个到服务器的新连接。如果您的服务速度非常快,它会立即突破其可用的本地端口范围。使用 Apache HttpClient
,您可以利用连接池。这将阻止您的应用程序看到您描述的问题。但是,鉴于您的服务能够比 Linux 内核从 TIME_WAIT
中取出套接字更快地响应,无论您做什么,连接池都会使您的客户端变慢(如果它没有减慢任何东西) down - 然后你会 运行 再次离开本地临时端口)。
虽然可以在 Linux 内核中启用 TCP 重用,但它可能会变得危险(数据包可能会延迟,您可能会让临时端口接收到它们不理解的随机数据包,这可能会导致各种问题)。这里的解决方案是像第二个示例中那样使用连接池,使用足够高的数字来实现接近您正在寻找的性能。
为了帮助您调整连接池,您需要调整 maxConnPerRoute
和 maxConnTotal
参数。 maxConnPerRoute
限制单个 IP:Port 对的连接数,maxTotal
限制将打开的总连接数。在您的情况下,由于所有请求似乎都是针对同一位置发出的,因此您可以将它们设置为相同的(高)值。