Кейс: под падал с too many open files из-за RestTemplate

Сервис ходил в соседний backend через RestTemplate, поднятый через new RestTemplate() без явного HTTP-клиента. Под нагрузкой в несколько сотен RPS под начал падать с IOException: too many open files, хотя дескрипторов на подключения к БД хватало с запасом.

Дефолтный RestTemplate использует SimpleClientHttpRequestFactory, который открывает новое TCP-соединение на каждый запрос через HttpURLConnection и не держит connection pool. Каждое соединение после ответа уходит в TIME_WAIT на уровне ОС вместо повторного использования — при высоком RPS сокеты накапливаются быстрее, чем система их закрывает.

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(50);

CloseableHttpClient client = HttpClients.custom() .setConnectionManager(cm) .build();

RestTemplate restTemplate = new RestTemplate( new HttpComponentsClientHttpRequestFactory(client) );

Спросят следом: почему дескрипторы кончились именно на этом сервисе, а не глобально по ОС? ulimit -n считается на процесс, и под с дефолтным лимитом в контейнере упирается в потолок быстрее, чем кажется по логам приложения. Ещё уточнят про keep-alive: без него соединение закрывается после каждого ответа, и проблема с TIME_WAIT становится ещё острее при частых запросах.

RestTemplate по умолчанию не пул, а фабрика новых соединений — тот же паттерн, что и SimpleAsyncTaskExecutor у @Async.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки