Spring boot + tomcat 8.5 + mongoDB, AsyncRequestTimeoutException - PullRequest
10 голосов
/ 11 июня 2019

Я создал весеннее загрузочное веб-приложение и развернул войну того же самого контейнера tomcat. Приложение подключается к mongoDB с использованием асинхронных подключений. Для этого я использую библиотеку mongodb-driver-async.

При запуске все работает нормально. Но как только нагрузка увеличивается, в соединениях с БД показывается следующее исключение:

org.springframework.web.context.request.async.AsyncRequestTimeoutException: null
        at org.springframework.web.context.request.async.TimeoutDeferredResultProcessingInterceptor.handleTimeout(TimeoutDeferredResultProcessingInterceptor.java:42)
        at org.springframework.web.context.request.async.DeferredResultInterceptorChain.triggerAfterTimeout(DeferredResultInterceptorChain.java:75)
        at org.springframework.web.context.request.async.WebAsyncManager$5.run(WebAsyncManager.java:392)
        at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.onTimeout(StandardServletAsyncWebRequest.java:143)
        at org.apache.catalina.core.AsyncListenerWrapper.fireOnTimeout(AsyncListenerWrapper.java:44)
        at org.apache.catalina.core.AsyncContextImpl.timeout(AsyncContextImpl.java:131)
        at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:157)

Я использую следующие версии программного обеспечения:

  1. Пружинная загрузка -> 1.5.4.RELEASE
  2. Tomcat (устанавливается как автономный двоичный файл) -> apache-tomcat-8.5.37
  3. Версия БД Mongo: v3.4.10
  4. mongodb-driver-async: 3.4.2

Как только я перезагружаю сервис tomcat, все начинает работать нормально.

Пожалуйста, помогите, что может быть основной причиной этой проблемы.

P.S .: Я использую DeferredResult и CompletableFuture для создания Async REST API.

Я также попытался использовать spring.mvc.async.request-timeout в приложении и настроил asynTimeout в Tomcat. Но все равно получаю ту же ошибку.

Ответы [ 2 ]

3 голосов
/ 13 июня 2019

Задачи Asynchroneus располагаются в очереди (пуле), которая обрабатывается параллельно в зависимости от количества выделенных потоков. Не все асинхронные задачи выполняются одновременно. Некоторые из них стоят в очереди. В такой системе AsyncRequestTimeoutException получает нормальное поведение .

Если вы заполняете очереди асинхронными задачами, которые невозможно выполнить под давлением. Увеличение таймаута только задержит проблему. Вместо этого вам следует сосредоточиться на проблеме:

  1. Сокращение времени выполнения (с помощью различных оптимизаций) асинхронной задачи. Это ослабит объединение асинхронных задач. Очевидно, что требуется кодирование.
  2. Увеличьте количество выделенных CPUS для более эффективного выполнения параллельных задач.
  3. Увеличение количества потоков, обслуживающих исполнителя драйвера.

Драйвер Mongo Async использует AsynchronousSocketChannel или Netty, если Netty находится в пути к классам. Чтобы увеличить количество рабочих потоков, обслуживающих асинхронную связь, вы должны использовать:

      MongoClientSettings.builder()
    .streamFactoryFactory(NettyStreamFactoryFactory(io.netty.channel.EventLoopGroup eventLoopGroup, 
io.netty.buffer.ByteBufAllocator allocator))
                       .build();

где eventLoopGroup будет io.netty.channel.nio.NioEventLoopGroup (int nThreads))

в NioEventLoopGroup вы можете установить количество потоков, обслуживающих вашу асинхронную связь

Подробнее о настройке Netty можно узнать здесь https://mongodb.github.io/mongo-java-driver/3.2/driver-async/reference/connecting/connection-settings/

3 голосов
/ 13 июня 2019

Очевидно, что Spring рассчитывает ваши запросы и выдает AsyncRequestTimeoutException, который возвращает 503 вашему клиенту.

Теперь вопрос в том, почему это происходит?Есть две возможности.

  1. Это допустимые тайм-ауты.Вы упомянули, что вы видите исключения только тогда, когда увеличивается нагрузка на ваш сервер.Поэтому, возможно, ваш сервер просто не может справиться с этой нагрузкой, и его производительность снизилась до такой степени, что некоторые запросы не могут быть выполнены до того, как Spring их истечет.

  2. Тайм-ауты вызваны вашимсерверу не удалось отправить ответ на асинхронный запрос из-за ошибки программирования, оставив запрос открытым до тех пор, пока Spring его не истечет.Это легко сделать, если ваш сервер плохо обрабатывает исключения.Если ваш сервер работает синхронно, это нормально, быть немного неаккуратным с обработкой исключений, потому что необработанные исключения будут распространяться до серверной среды, которая отправит ответ клиенту.Но если вам не удается обработать исключение в каком-то асинхронном коде, это исключение будет обнаружено в другом месте (возможно, в некотором коде управления пулом потоков), и у этого кода нет возможности узнать, что существует асинхронный запрос, ожидающий результата операцииЭто вызвало исключение.

Трудно понять, что может происходить, не зная больше о вашем приложении.Но есть некоторые вещи, которые вы можете исследовать.

Сначала попробуйте поискать исчерпание ресурсов.

  • Работает ли сборщик мусора постоянно?
  • Все ли процессорыпривязано к 100%?
  • Сильно ли меняется ОС?
  • Если сервер баз данных находится на отдельном компьютере, на этом компьютере наблюдаются признаки исчерпания ресурсов?
  • Сколькосоединения открыты для базы данных?Если есть пул соединений, он исчерпан?
  • Сколько потоков запущено?Если на сервере есть пулы потоков, не превышены ли они?

Если что-то ограничено, то, возможно, именно узкое место приводит к истечению времени ожидания ваших запросов.

Попробуйтеустановите spring.mvc.async.request-timeout в -1 и посмотрите, что произойдет.Получаете ли вы теперь ответы на каждый запрос, только медленно, или кажется, что некоторые запросы зависают навсегда?Если это последнее, это настоятельно указывает на наличие ошибки на вашем сервере, которая приводит к тому, что он теряет отслеживание запросов и не может отправлять ответы.(Если настройка spring.mvc.async.request-timeout, похоже, не имеет никакого эффекта, то следующее, что вам следует выяснить, действительно ли работает механизм, который вы используете для настройки конфигурации.)

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

Аналогичная стратегия заключается в сохранении каждого идентификатора запроса в карте, в которой значениеэто объект, который отслеживает, когда запрос был запущен и что ваш сервер последний раз делал с этим запросом.(В этом случае ваш сервер обновляет эту карту на каждой контрольной точке вместо записи в журнал или в дополнение к ней.) Вы можете настроить фильтр для генерации идентификаторов запросов и ведения карты.Если ваш фильтр видит, что сервер отправляет ответ 5xx, вы можете записать последнее действие для этого запроса с карты.

Надеюсь, это поможет!

...