Apache HttpClient 4.2.1
У меня есть процесс Java, который загружает несколько файлов с одной CouchDB A и загружает их на другую CouchDB B. Загрузка и выгрузка выполняются с использованием 40 рабочих потоков с использованием ExecutorService.
Когда число файлов велико (~ 25k), процесс выдает исключение SocketException «слишком много открытых файлов» из-за ограничения дескриптора открытого файла Linux. Я подумал, что это может быть связано с утечкой ресурсов, и приступил к анализу кода, который выглядит следующим образом:
Инициирование рабочих потоков
private boolean copyDocumentsFromAtoB(Array ids) {
ExecutorService downloader = Executors.newFixedThreadPool(50);
ExecutorService uploader = Executors.newFixedThreadPool(50);
for (String id: ids) {
downloader.execute(new DownloaderThread(id, downloadedDocs));
}
for (JsonElement doc: downloadedDocs) {
uploader.execute(new UploaderThread(doc));
}
}
Класс Runnable Downloader (класс UploaderThread имеет аналогичную реализацию)
private static final class DocumentDownloader implements Runnable {
private final String documentId;
private final JsonArray downloadedDocs;
DocumentDownloader(String documentId, JsonArray downloadedDocs) {
this.documentId = documentId;
this.downloadedDocs = downloadedDocs;
}
@Override
public void run() {
InputStream docStream = null;
String url = buildUrl(documentId);
HttpGet doc = new HttpGet(url);
try {
//doc.setHeaders()
HttpClient httpClient = new DefaultHttpClient();
HttpResponse docResponse = httpClient.execute(doc);
docStream = docResponse.getEntity().getContent();
//document parsing and adding to downloadedDocs. Not important
} catch (Exception e) {
//handle exceptions
} finally {
if (docStream != null) {
try {
docStream.close();
} catch (IOException e) {
LOGGER.debug("Cannot close input stream", e);
}
}
}
}
}
Выводы:
- HttpClient является локальным для каждого потока. Это неоптимально по мнению многих ресурсов и сообщений, которые я нашел в Интернете.
- Если я сделал,
httpClient.getConnectionManager().shutdown();
в конце класса Runnable, количество открытых файлов уменьшилось.
- Использование глобального HttpClient (создается только один раз в методе copyDocumentsFromAtoB и передается классам Runnable через конструкторы) с экземпляром PoolingClientConnectionManager и объектом локального контекста, дополнительно уменьшая количество открытых файлов.
Вопросы:
Почему в исходной реализации увеличилось количество дескрипторов открытых файлов в процессе копирования? Как несколько экземпляров HttpClient способствуют этому?
Не закрывает ли docStream.close();
в блоке finally
какое-либо соединение Http, которое создается между процессом и базой данных?
Разрушается ли экземпляр HttpClient (освобождая все открытые ресурсы в процессе), когда поток, к которому он принадлежит, завершается? (Это объясняет, почему количество открытых файлов уменьшается после преждевременного завершения процесса копирования)
Могу ли я выполнить какие-либо другие оптимизации (с точки зрения утечки ресурсов), кроме использования одного глобального HttpClient?
Существуют ли инструменты, которые я мог бы использовать для получения количественной статистики этого сценария для различных реализаций?
Какие шаги я могу выполнить, чтобы найти оптимальное количество работников
резьбы setMaxTotal
и setDefaultMaxPerRoute
(для
PoolingClientConnectionManager)