Подтвердить что ты не робот

Используя шаблон 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 из библиотеки Apache Http Components.

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

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


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

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

4b9b3361

Ответ 1

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

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

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

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