使用Spring REST模板,创建太多连接或速度太慢

2022-09-01 07:42:35

我有一个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)上进行了测试,由于端口范围不同,在不同时间也有类似的结果。

为了解决这个问题,我尝试将Apache Http Components库中的HttpClient与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,但看起来客户端创建的连接数仍不超过一百个(从判断)。REST 服务是使用 JAX-RS 实现的,并在 Jetty 上运行。netstat -n | wc -l

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


答案 1

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

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

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

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


答案 2

推荐