Apache HttpClient 4.x - Многопоточное выполнение и утечка ресурсов - PullRequest
0 голосов
/ 06 сентября 2018

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);
                    }
                }
            }
        }
    }

Выводы:

  1. HttpClient является локальным для каждого потока. Это неоптимально по мнению многих ресурсов и сообщений, которые я нашел в Интернете.
  2. Если я сделал, httpClient.getConnectionManager().shutdown(); в конце класса Runnable, количество открытых файлов уменьшилось.
  3. Использование глобального HttpClient (создается только один раз в методе copyDocumentsFromAtoB и передается классам Runnable через конструкторы) с экземпляром PoolingClientConnectionManager и объектом локального контекста, дополнительно уменьшая количество открытых файлов.

Вопросы:

  1. Почему в исходной реализации увеличилось количество дескрипторов открытых файлов в процессе копирования? Как несколько экземпляров HttpClient способствуют этому?

  2. Не закрывает ли docStream.close(); в блоке finally какое-либо соединение Http, которое создается между процессом и базой данных?

  3. Разрушается ли экземпляр HttpClient (освобождая все открытые ресурсы в процессе), когда поток, к которому он принадлежит, завершается? (Это объясняет, почему количество открытых файлов уменьшается после преждевременного завершения процесса копирования)

  4. Могу ли я выполнить какие-либо другие оптимизации (с точки зрения утечки ресурсов), кроме использования одного глобального HttpClient?

  5. Существуют ли инструменты, которые я мог бы использовать для получения количественной статистики этого сценария для различных реализаций?

  6. Какие шаги я могу выполнить, чтобы найти оптимальное количество работников резьбы setMaxTotal и setDefaultMaxPerRoute (для PoolingClientConnectionManager)

...