【问题标题】:1000+ threads blocks on Tomcat response processTomcat响应过程中的1000多个线程块
【发布时间】:2018-10-03 08:56:32
【问题描述】:

问题第一:

我的服务响应时间随着并发的增加而增加。我在tomcat响应客户端的步骤中发现了1000多个线程块。但我不知道如何解决它。

背景:

我的服务是一个嵌入了 tomcat 的 Spring Boot 项目。 (tomcat-embed-core-8.5.23)

我的实验是使用 JMeter 作为客户端,创建 2000 个线程向我的服务发送请求 10 轮(总共 20000 个请求)。在我的服务之后,每个请求都有一个存根服务以 2 秒的延迟响应。 (即 JMeter(2000 线程)=> 我的服务 => Stub Server)

实验数据

  • 在我的服务中,我设置了“server.tomcat.max-threads=2000”。所以有 2000 多个名为“http-nio-8080*”的线程
  • 结果是,最大响应时间>30 s。这是从 CLI 复制的,

    摘要 = 00:01:23 中的 20000 = 241.7/s 平均值:6890 最小值:2288 最大值:31158 错误:0 (0.00%)

最小响应时间 (2288ms) 符合预期(因为存根服务器的成本为 2000ms)。但是最大响应时间(31158 毫秒)太长了。我分析了jstack日志。在同一个锁上阻塞了 1121 个线程。 (我附上了其中一个栈内容。而且我认为是我的服务响应JMeter的时间点。但不幸的是有一个SynchronizedStack阻塞了1000+线程)

我有证据表明锁是响应时间的真正杀手。在 9 秒后的另一个 jstack 日志中,仍然有 1058 个线程锁在同一个锁上。

"http-nio-8080-exec-3988" #4038 daemon prio=5 os_prio=0 tid=0x00007fdec045c800 nid=0x1050 waiting for monitor entry [0x00007fdb6c94e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
                at org.apache.tomcat.util.collections.SynchronizedStack.pop(SynchronizedStack.java:75)
                - waiting to lock <0x00000000dac7afd0> (a org.apache.tomcat.util.collections.SynchronizedStack)
                at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:89)
                at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:157)
                at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1267)
                at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:670)
                at org.apache.tomcat.util.net.SocketWrapperBase.flushBlocking(SocketWrapperBase.java:607)
                at org.apache.tomcat.util.net.SocketWrapperBase.flush(SocketWrapperBase.java:597)
                at org.apache.coyote.http11.Http11OutputBuffer.flushBuffer(Http11OutputBuffer.java:581)
                at org.apache.coyote.http11.Http11OutputBuffer.flush(Http11OutputBuffer.java:272)
                at org.apache.coyote.http11.Http11Processor.flush(Http11Processor.java:1560)
                at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:283)
                at org.apache.coyote.Response.action(Response.java:173)
                at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:317)
                at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:284)
                at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:118)
                at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
                at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
                - locked <0x00000000fe000000> (a java.io.OutputStreamWriter)
                at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
                at org.springframework.util.StreamUtils.copy(StreamUtils.java:119)
                at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106)
                at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:41)
                at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227)
                at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:247)
                at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174)
                at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
                at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:113)
                at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
                at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
                at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
                at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
                at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
                at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
                at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
                at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
                at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
                at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
                at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
                at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
                at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
                at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
                - locked <0x00000000e1a59188> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
                at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
                - <0x00000000df55e210> (a java.util.concurrent.ThreadPoolExecutor$Worker)

【问题讨论】:

    标签: multithreading spring-boot embedded-tomcat-8


    【解决方案1】:

    您错误地允许 tomcat 过度使用。

    首先,将 tomcat 执行程序线程限制在最大 100 或 200 个左右。 实际上,任何 jvm 中的线程都不应该超过 200 个,除非您真的知道自己在做什么并且有 99% 的线程在等待(而不是阻塞监视器条目)。

    然后,限制 tcp SYN 队列长度(serversocket 接受队列),以确保在建立太多 jmeter 连接时不会接受所有的连接。

    从严格限制的线程开始,只要您在网络带宽和 CPU 上都处于空闲状态,甚至只要响应时间不增加,就可以增加线程数。

    【讨论】:

    • 感谢您的分享经验。我同意限制并发可以缩短响应时间。但在此之前,我仍然想在 2000 线程解决方案上多花一点时间。我猜如果没有这样的tomcat SynchronizedStack 锁,我的服务会响应更快。有什么建议吗?
    • 这听起来很奇怪,但是如果您可以尝试使用 jetty,您可能会发现竞争的 exec 线程有所不同。这里的这个问题似乎在服务器的实现中很深。我不认为你对这个锁的事情有太多的控制权。另一个建议:尝试旧的 http11 连接器,而不是 NIO 连接器。您可能会对操作系统处理每个套接字 1 个线程的能力感到惊讶;使用不同的连接器 impl 可以避免单个 nio 漏斗上 2000 个线程的巨大争用问题。
    • 抱歉迟到了。我尝试了 3 种解决方案。 1.在tomcat中使用“BIO”。我没有在“jstack log”中看到线程阻塞。但是在 JMeter 中发现了很多连接被拒绝的错误(我将连接超时设置为 10 秒)。所以“BIO”不是我的选择。 2.使用jetty替换tomcat。 Jetty 真的比 tomcat 快 10%。 3. 将我的服务的 CPU 资源从 2 核增加到 3 核。它确实显着提高了性能。 (JMeter 2000 线程从 30s 到 8s;JMeter 1000 线程从 13s 到 3s)
    • 始终将背压置于系统入口处,否则您会过度使用。在您的客户端(jmeter)被拒绝是负载均衡器用来分派到另一台服务器的正常优雅降级。您当然可以选择排队并减慢服务时间,但您需要对此进行限制,无论是缓冲大量延迟请求所需的时间还是内存。在我的书中,要么我可以在 1 秒内开始服务(不包括服务时间),要么拒绝。这意味着队列不超过您的最大速率。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 2014-06-19
    • 2020-10-06
    • 1970-01-01
    • 2011-07-19
    • 2011-05-26
    • 2011-02-20
    相关资源
    最近更新 更多