Используя шаблон Spring REST, либо создавая слишком много соединений, либо медленно

у меня есть RESTful-сервис, который работает очень быстро. Я тестирую его на localhost. Клиент использует шаблон 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 с HttpClientBuilder из библиотеки компонентов Http Apache.

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 command, я вижу, что на сервере не так много подключений. Я попытался установить цифры в несколько тысяч, но, похоже, клиент никогда не использует так много.

есть ли что-нибудь, что я могу сделать, чтобы улучшить производительность, не открывая слишком много соединений?


UPDATE: я пробовал установить номер всего и на один маршрут подключений до 5000 и 2500, но все равно похоже, что клиент не создает больше сотни (судя по netstat -n | wc -l). Сервис REST реализован с использованием JAX-RS и работает на причале.

UPDATE2: теперь я настроил сервер с некоторыми настройками памяти, и я получаю действительно хорошую пропускную способность. Наивный подход все еще немного быстрее, но я думаю, что это просто немного накладные расходы на объединение на стороне клиента.

1 ответов


На самом деле Spring Boot не протекает соединения. То, что вы видите здесь, - это стандартное поведение ядра Linux (и каждой основной ОС). Все сокеты, которые закрыты от машины, идут в TIME_WAIT состояние в течение некоторого времени. Это делается для того, чтобы следующий сокет, использующий этот эфемерный порт, не получал пакеты, которые на самом деле предназначались для предыдущего сокета на этом порту. Разница, которую вы видите между ними, является результатом подходов пула соединений к каждому из них принимает.

более конкретно, RestTemplate не по умолчанию используется пул соединений. Это означает, что каждый вызов rest открывает новый локальный эфемерный порт и новое соединение с сервером. Если ваше обслуживание очень быстро, то оно дунет через свой доступный местный ряд порта в никаком времени на всех. С Apache HttpClient, вы используете преимущества объединения соединений. Это предотвратит ваше приложение от просмотра проблемы, которую вы описали. Однако, учитывая, что ваш сервис способна реагировать быстрее, чем ядро Linux занимает гнезда из TIME_WAIT, пул соединений сделает ваш клиент медленнее независимо от того, что вы делаете (если он ничего не замедлил - тогда у вас снова закончатся локальные эфемерные порты).

хотя можно включить повторное использование TCP в ядре Linux, это может стать опасным (пакеты могут задерживаться, и вы можете получить эфемерные порты, получающие случайные пакеты, которые они не понимают, что может вызвать все виды проблем). Этот решение здесь-использовать пул соединений, как во втором примере, с достаточно высокими номерами для достижения производительности, которую вы ищете.

чтобы помочь вам настроить пул соединений, вы захотите настроить maxConnPerRoute и maxConnTotal параметры. maxConnPerRoute ограничивает количество подключений, которые будут сделаны к одной паре IP:Port, и maxTotal ограничивает количество общих соединений, которые когда-либо будут открыты. В вашем случае, поскольку кажется, что все запросы сделаны в том же месте вы можете установить для них одинаковое (высокое) значение.