Загрузка больших файлов приводит к превышению лимита накладных расходов GC

#java #playframework #playframework-2.3

#java #playframework #playframework-2.3

Вопрос:

У меня есть две службы, первая frontend_service и вторая backend_service, и я получаю большой файл из backend_service и пытаюсь переслать пользователю через frontend_service, используя response.getBodyAsStream(), но это вызывает «java.lang.OutOfMemoryError: превышен лимит накладных расходов GC» во frontend_service.

код для backend_service:

`

 public static Result downloadLargeFile(String filePath){
   File file = new File(filePath);
   InputStream inputStream = new FileInputStream(file);
   return ok(inputStream);
}
  

`

код для frontend_service:

`

   public static F.Promise<Result> downloadLargeFile(String filePath) {
       //this will call backend_service downloadLargeFile method.
       String backEndUrl = getBackEndUrl(filePath);
       return getInputStream(backEndUrl);
    }
  

`

`

 public static Promise<Result> getInputStream(String url) {
            return WS.url(url).get().map(
                    response -> {
                        InputStream inputStream =  response.getBodyAsStream();
                        return ok(inputStream);
                    }
            );
}
  

`

Я попробовал предложенное здесь решение, прочитав несколько байтов за раз из InputStream и создав tmp-файл во frontend_service и отправив tmp-файл в качестве выходных данных из frontend_service.

 `

    public static Promise<Result> getInputStream(String url) {
            return WS.url(url).get().map(
                    response -> {
                        InputStream inputStream = null;
                        OutputStream outputStream = null;
                        try {
                            inputStream =  response.getBodyAsStream();
                            //write input stream to tmp file
                            final File tmpFile = new File("/tmp/tmp.txt");
                            outputStream = new FileOutputStream(tmpFile);

                            int read = 0;
                            byte[] buffer = new byte[500];
                            while((read = inputStream.read(buffer)) != -1){
                                outputStream.write(buffer, 0 , read);
                            }
                            return ok(tmpFile);
                        } catch (IOException e) {
                            e.printStackTrace();
                            return badRequest();
                        } finally {
                            if (inputStream != null) {inputStream.close();}
                            if (outputStream != null) {outputStream.close();}
                        }
                    }
            );

`
  

Приведенный выше код также выдает java.lang.Ошибка OutOfMemoryError. Я пытаюсь использовать файл объемом 1 ГБ.

Комментарии:

1. reading few bytes at a time from inputStream это единственное решение, о котором я могу думать. Можете ли вы опубликовать, что пошло не так с этим.

2. Я отредактировал вопрос.

3. Я подозреваю return ok(tmpFile); , что это вызывает ошибку. Можете ли вы вернуть что-то еще, чтобы проверить теорию. (Может быть файл меньшего размера из файловой системы).

4. Спасибо за предложение. Я пробовал ok("test") , но та же ошибка (java.lang. Ошибка OutOfMemoryError). Похоже, что InputStream ожидает хранения всех данных вместо перехода к следующему выполнению и предоставления данных OutputStream для записи.

Ответ №1:

У меня нет реализации «под рукой», поэтому я напишу алгоритм.

1. Play использует AsyncHttpClient under the WS . Вам нужно получить его или создать, как описано в https://www.playframework.com/documentation/2.3.x/JavaWS#Using-WSClient

2. Затем вам необходимо реализовать AsyncCompletionHandler , как в описании класса https://static.javadoc.io/org.asynchttpclient/async-http-client/2.0.0/org/asynchttpclient/AsyncHttpClient.html

3. В onBodyPartReceived методе AsyncCompletionHandler класса вам нужно поместить часть тела в ответ на фрагментированное воспроизведение. Ответы с изменениями, описанные здесь: https://www.playframework.com/documentation/2.3.x/JavaStream#Chunked-responses

Постскриптум

Обсуждение аналогичного решения, но в противоположном направлении — потоковая загрузка в службу «backend» (Amazon) через службу «frontend» (play 2): https://groups.google.com/d/msg/asynchttpclient/EpNKLSG9ymM/BAGvwl0Wby8J