Spring Boot + tomcat 8.5 + mongoDB,AsyncRequestTimeoutException

时间:2019-06-11 11:17:38

标签: java spring mongodb spring-boot tomcat8.5

我创建了一个Spring Boot Web应用程序并将相同的战争部署到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. 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。但是仍然出现相同的错误。

2 个答案:

答案 0 :(得分:3)

很明显,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响应,则可以从地图中记录该请求的最后一个操作。

希望这会有所帮助!

答案 1 :(得分:3)

异步任务安排在一个队列(池)中,该队列根据分配的线程数并行处理。并非所有异步任务都在同一时间执行。其中一些排队。在这样的系统中,获取AsyncRequestTimeoutException是正常行为

如果要用无法在压力下执行的异步任务填充队列。增加超时只会延迟问题。您应该专注于该问题:

  1. 减少异步任务的执行时间(通过各种优化)。这将放松异步任务的池。显然需要编码。
  2. 增加分配的CPUS数量,以便能够更有效地运行并行任务。
  3. 增加服务驱动程序执行器的线程数。

如果在类路径中找到Netty,则Mongo Async驱动程序正在使用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上,您可以设置服务于异步通信的线程数

https://mongodb.github.io/mongo-java-driver/3.2/driver-async/reference/connecting/connection-settings/

上了解有关Netty配置的更多信息。