Весенняя загрузка + tomcat 8.5 + mongoDB, исключение AsyncRequestTimeoutException

Я создал веб-приложение с весенней загрузкой и развернул его в контейнере 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.РЕЛИЗ
  2. Tomcat (установлен как отдельный двоичный файл) -> apache-tomcat-8.5.37
  3. Версия БД Монго: v3.4.10
  4. mongodb-драйвер-асинхронный: 3.4.2

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

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

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

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


person Sachin Gupta    schedule 11.06.2019    source источник
comment
Вы можете попробовать установить для spring.mvc.async.request-timeout высокое значение и посмотреть, поможет ли это. Вы знаете, какая операция занимает много времени?   -  person Simon Martinelli    schedule 11.06.2019
comment
Да я уже пробовал, не помогает   -  person Sachin Gupta    schedule 11.06.2019
comment
И знаете ли вы, какие операции вызывают проблему?   -  person Simon Martinelli    schedule 11.06.2019
comment
Во всех операциях   -  person Sachin Gupta    schedule 11.06.2019
comment
Может ли это быть связано с этой веткой? stackoverflow.com/questions/39856198 /   -  person Bentaye    schedule 13.06.2019
comment
Я уже пробовал это, но безуспешно   -  person Sachin Gupta    schedule 13.06.2019


Ответы (2)


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

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

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

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

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

Во-первых, попробуйте найти исчерпание ресурсов.

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

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

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

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

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

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

person Willis Blackburn    schedule 13.06.2019
comment
+1 за стратегию с уникальным идентификатором. Многопоточное/приложение с асинхронным вызовом всегда сложно отлаживать и, следовательно, очень сложно определить проблемную область/коренную причину - person Bond - Java Bond; 18.06.2019
comment
Спасибо, что отметили этот ответ как правильный! В чем вы обнаружили проблему? - person Willis Blackburn; 21.06.2019

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

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

  1. Сократите время выполнения (путем различных оптимизаций) асинхронной задачи. Это ослабит объединение асинхронных задач. Это явно требует кодирования.
  2. Увеличьте количество выделенных ЦП, чтобы иметь возможность более эффективно выполнять параллельные задачи.
  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/

person Alexander Petrov    schedule 13.06.2019