Spring 开机 + tomcat 8.5 + mongoDB, AsyncRequestTimeoutException
Spring boot + tomcat 8.5 + mongoDB, AsyncRequestTimeoutException
我已经创建了一个 spring 引导 Web 应用程序并将相同的 war 部署到 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.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)
我正在使用以下版本的软件ware:
- Spring 启动 -> 1.5.4.RELEASE
- Tomcat(作为独立的二进制文件安装)-> apache-tomcat-8.5.37
- Mongo数据库版本:v3.4.10
- mongodb-驱动程序异步:3.4.2
只要我重新启动 tomcat 服务,一切就会开始正常工作。
请帮忙,这个问题的根本原因是什么。
P.S.: 我正在使用 DeferredResult
和 CompletableFuture 来创建异步 REST API.
我也试过在应用程序中使用 spring.mvc.async.request-timeout
并在 tomcat 中配置 asynTimeout
。但仍然出现同样的错误。
可能很明显 Spring 使您的请求超时并抛出 AsyncRequestTimeoutException
,returns 将 503 返回给您的客户端。
现在的问题是,为什么会这样?有两种可能。
这些是合法的超时。您提到只有在服务器负载增加时才会看到异常。因此,您的服务器可能无法处理该负载,并且其性能已经下降到某些请求在 Spring 超时之前无法完成的程度。
超时是由于您的服务器由于编程错误而未能发送对异步请求的响应,使请求保持打开状态,直到 Spring 最终超时。如果您的服务器不能很好地处理异常,很容易发生这种情况。如果您的服务器是同步的,那么在异常处理方面马虎一点也没关系,因为未处理的异常会传播到服务器框架,服务器框架会将响应发送回客户端。但是如果你在一些异步代码中没有处理异常,那个异常会在别处被捕获(可能在一些线程池管理代码中),并且那些代码没有办法知道有一个异步请求在等待操作的结果抛出异常。
如果不进一步了解您的应用程序,就很难弄清楚可能会发生什么。但有些事情您可以调查。
首先,尝试查找资源耗尽。
- 垃圾收集器一直运行吗?
- 是否所有 CPU 都固定在 100%?
- OS 是否大量交换?
- 如果数据库服务器在单独的机器上,那台机器是否有资源耗尽的迹象?
- 数据库打开了多少个连接?如果有连接池,是不是已经maxed out了?
- 有多少线程运行?如果服务器中有线程池,它们是否已满?
如果某些东西已达到极限,那么可能是导致您的请求超时的瓶颈。
尝试将 spring.mvc.async.request-timeout
设置为 -1,看看会发生什么。您现在是否会收到每个请求的响应,只是速度很慢,或者某些请求似乎永远挂起?如果是后者,这强烈表明您的服务器中存在错误,导致它无法跟踪请求并无法发送响应。 (如果设置 spring.mvc.async.request-timeout
似乎没有效果,那么接下来您应该调查的是您用于设置配置的机制是否真的有效。)
我发现在这些情况下有用的策略是为每个请求生成一个唯一的 ID,并在每次服务器进行异步调用或从异步请求接收响应时写入该 ID 以及一些上下文信息调用,以及异步处理程序中的各种检查点。如果请求丢失,您可以使用日志信息找出请求 ID 以及服务器最后对该请求执行的操作。
类似的策略是将每个请求 ID 保存到一个映射中,其中的值是一个对象,用于跟踪请求何时开始以及您的服务器最后对该请求执行的操作。 (在这种情况下,您的服务器会在每个检查点更新此地图,而不是写入日志,或者除了写入日志之外。)您可以设置过滤器来生成请求 ID 并维护地图。如果您的过滤器发现服务器发送了 5xx 响应,您可以从地图中记录该请求的最后一个操作。
希望对您有所帮助!
异步任务安排在队列(池)中,根据分配的线程数并行处理。并不是所有的异步任务都是同时执行的。他们中的一些人正在排队。在这样的系统中,获取 AsyncRequestTimeoutException 是 正常行为。
如果您正在用无法在压力下执行的异步任务填充队列。增加超时只会延迟问题。你应该专注于问题:
- 减少异步任务的执行时间(通过各种优化)。这将放宽异步任务的池化。它显然需要编码。
- 增加分配的 CPU 数量,以便能够 运行 更有效地执行并行任务。
- 增加为驱动程序的执行程序服务的线程数。
Mongo 异步驱动程序正在使用 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/
我已经创建了一个 spring 引导 Web 应用程序并将相同的 war 部署到 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.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)
我正在使用以下版本的软件ware:
- Spring 启动 -> 1.5.4.RELEASE
- Tomcat(作为独立的二进制文件安装)-> apache-tomcat-8.5.37
- Mongo数据库版本:v3.4.10
- mongodb-驱动程序异步:3.4.2
只要我重新启动 tomcat 服务,一切就会开始正常工作。
请帮忙,这个问题的根本原因是什么。
P.S.: 我正在使用 DeferredResult
和 CompletableFuture 来创建异步 REST API.
我也试过在应用程序中使用 spring.mvc.async.request-timeout
并在 tomcat 中配置 asynTimeout
。但仍然出现同样的错误。
可能很明显 Spring 使您的请求超时并抛出 AsyncRequestTimeoutException
,returns 将 503 返回给您的客户端。
现在的问题是,为什么会这样?有两种可能。
这些是合法的超时。您提到只有在服务器负载增加时才会看到异常。因此,您的服务器可能无法处理该负载,并且其性能已经下降到某些请求在 Spring 超时之前无法完成的程度。
超时是由于您的服务器由于编程错误而未能发送对异步请求的响应,使请求保持打开状态,直到 Spring 最终超时。如果您的服务器不能很好地处理异常,很容易发生这种情况。如果您的服务器是同步的,那么在异常处理方面马虎一点也没关系,因为未处理的异常会传播到服务器框架,服务器框架会将响应发送回客户端。但是如果你在一些异步代码中没有处理异常,那个异常会在别处被捕获(可能在一些线程池管理代码中),并且那些代码没有办法知道有一个异步请求在等待操作的结果抛出异常。
如果不进一步了解您的应用程序,就很难弄清楚可能会发生什么。但有些事情您可以调查。
首先,尝试查找资源耗尽。
- 垃圾收集器一直运行吗?
- 是否所有 CPU 都固定在 100%?
- OS 是否大量交换?
- 如果数据库服务器在单独的机器上,那台机器是否有资源耗尽的迹象?
- 数据库打开了多少个连接?如果有连接池,是不是已经maxed out了?
- 有多少线程运行?如果服务器中有线程池,它们是否已满?
如果某些东西已达到极限,那么可能是导致您的请求超时的瓶颈。
尝试将 spring.mvc.async.request-timeout
设置为 -1,看看会发生什么。您现在是否会收到每个请求的响应,只是速度很慢,或者某些请求似乎永远挂起?如果是后者,这强烈表明您的服务器中存在错误,导致它无法跟踪请求并无法发送响应。 (如果设置 spring.mvc.async.request-timeout
似乎没有效果,那么接下来您应该调查的是您用于设置配置的机制是否真的有效。)
我发现在这些情况下有用的策略是为每个请求生成一个唯一的 ID,并在每次服务器进行异步调用或从异步请求接收响应时写入该 ID 以及一些上下文信息调用,以及异步处理程序中的各种检查点。如果请求丢失,您可以使用日志信息找出请求 ID 以及服务器最后对该请求执行的操作。
类似的策略是将每个请求 ID 保存到一个映射中,其中的值是一个对象,用于跟踪请求何时开始以及您的服务器最后对该请求执行的操作。 (在这种情况下,您的服务器会在每个检查点更新此地图,而不是写入日志,或者除了写入日志之外。)您可以设置过滤器来生成请求 ID 并维护地图。如果您的过滤器发现服务器发送了 5xx 响应,您可以从地图中记录该请求的最后一个操作。
希望对您有所帮助!
异步任务安排在队列(池)中,根据分配的线程数并行处理。并不是所有的异步任务都是同时执行的。他们中的一些人正在排队。在这样的系统中,获取 AsyncRequestTimeoutException 是 正常行为。
如果您正在用无法在压力下执行的异步任务填充队列。增加超时只会延迟问题。你应该专注于问题:
- 减少异步任务的执行时间(通过各种优化)。这将放宽异步任务的池化。它显然需要编码。
- 增加分配的 CPU 数量,以便能够 运行 更有效地执行并行任务。
- 增加为驱动程序的执行程序服务的线程数。
Mongo 异步驱动程序正在使用 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/