Spring Boot + Tomcat 8.5 + MongoDB,AsyncRequestTimeoutException

11

我创建了一个Spring Boot Web应用程序,并将其war部署到Tomcat容器中。 该应用程序使用异步连接连接到mongoDB。为此,我使用了mongodb-driver-async库。

在启动时一切正常。但是随着负载的增加,DB连接会显示以下异常:

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. Spring Boot -> 1.5.4.RELEASE
  2. Tomcat (已安装为独立二进制文件) -> apache-tomcat-8.5.37
  3. Mongo DB 版本: v3.4.10
  4. mongodb-driver-async: 3.4.2

只要我重新启动Tomcat服务,一切就正常运行。

请帮忙找出问题的根本原因。

P.S .: 我正在使用DeferredResult和CompletableFuture来创建异步REST API。

我还尝试在应用程序中使用spring.mvc.async.request-timeout并配置了Tomcat的asynTimeout,但仍然出现相同的错误。


你可以尝试将spring.mvc.async.request-timeout设置为较高的值,看看是否有帮助。你知道哪个操作需要花费很长时间吗? - Simon Martinelli
是的,我已经尝试过了,但它并没有帮助。 - Sachin Gupta
你知道是哪个操作导致了问题吗? - Simon Martinelli
在所有操作中 - Sachin Gupta
这与这个帖子有关吗?https://dev59.com/2FkS5IYBdhLWcg3wcGWW - Bentaye
我已经尝试过这个了,但是没有成功。 - Sachin Gupta
2个回答

5
显然,Spring正在超时处理您的请求并抛出“AsyncRequestTimeoutException”,这会向客户端返回503错误。
现在问题是,为什么会发生这种情况?有两种可能性。
1.这些是合法的超时。您提到只有在服务器负载增加时才会看到异常。因此,很可能是您的服务器无法处理该负载,并且其性能已降至某些请求无法在Spring超时之前完成的地步。
2.由于编程错误,导致您的服务器未能对异步请求发送响应而超时,直到Spring最终超时。如果您的服务器不擅长处理异常,则很容易发生这种情况。如果您的服务器是同步的,则可以稍微粗心地处理异常,因为未处理的异常将传播到服务器框架中,后者将向客户端发送响应。但是,如果在某些异步代码中未能处理异常,那么该异常将在其他地方(可能是一些线程池管理代码)捕获,而没有办法让该代码知道等待操作结果的异步请求存在。
不了解您的应用程序可能会很难确定可能发生的情况。但是有一些事情您可以调查。
首先,请尝试查找资源耗尽的原因。
- 垃圾回收器是否一直在运行? - 所有CPU是否都卡在100%? - 操作系统是否频繁交换内存? - 如果数据库服务器位于单独的计算机上,那么该计算机是否显示资源耗尽的迹象? - 有多少个连接打开到数据库?如果存在连接池,它是否已达到极限? - 有多少个线程正在运行?如果服务器中有线程池,它们是否已达到极限?
如果某些内容已经达到极限,则可能是导致请求超时的瓶颈。
请尝试将“spring.mvc.async.request-timeout”设置为-1,然后查看会发生什么。您现在是否会为每个请求收到响应,只是速度很慢,或者某些请求似乎永远挂起?如果是后者,则强烈暗示您的服务器存在错误,导致它丢失了请求并未能发送响应。(如果设置“spring.mvc.async.request-timeout”没有效果,则下一步您需要调查的是您用于设置配置的机制是否实际有效。)
在这种情况下,我发现一种有用的策略是为每个请求生成唯一ID,并在服务器进行异步调用或从异步调用接收响应时,写入ID以及异步处理程序中各种检查点处的一些上下文信息。如果请求丢失,则可以使用日志信息查找请求ID以及服务器最后使用该请求的情况。
一个类似的策略是将每个请求ID保存在一个映射中,其中值是跟踪请求何时启动以及您的服务器最后处理该请求的对象。(在这种情况下,您的服务器在每个检查点更新此映射,而不是或者除了写入日志之外)。您可以设置过滤器来生成请求ID并维护映射。如果您的过滤器看到服务器发送5xx响应,则可以从映射中记录该请求的最后操作。
希望这能帮助您!

2
使用唯一标识符的策略加1。多线程/具有异步调用的应用程序始终很难调试,因此很难确定问题区域/根本原因。 - Bond - Java Bond
谢谢您将这个答案标记为正确的!您发现问题是什么了吗? - Willis Blackburn

2
异步任务被安排在一个队列(池)中,根据分配的线程数并行处理。并非所有异步任务都同时执行,其中一些被排队等待。在这样的系统中,获取AsyncRequestTimeoutException是正常行为。
如果您填充了无法在压力下执行的异步任务队列,则增加超时时间只会延迟问题。相反,应该集中精力解决问题:
1. 通过各种优化减少异步任务的执行时间。这将放松异步任务的池化。显然需要编码。 2. 增加分配的CPU数量,以便更有效地运行并行任务。 3. 增加服务于驱动程序执行器的线程数。
Mongo Async driver使用AsynchronousSocketChannel或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/

原始答案: "最初的回答"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接