避免在tomcat/spring-boot中由慢客户端引起的线程饥饿

Mar*_*tin 5 java tomcat nio

我有一个简单的spring-boot应用程序.它有一个端点,它从请求体中获取一个对象,并且什么都不做:

@Controller
class FooController {
    @RequestMapping(method=RequestMethod.POST, value="/foo")
    public void postFoo(@RequestBody Foo foo) {
    }
}
Run Code Online (Sandbox Code Playgroud)

非常简单的东西.

然后我通过telnet连接并通过适当的标头发送,好像我要发送一个json编码的对象,但从不发送请求体 - 我只是让连接挂起.

运行jstack,我可以看到tomcat已经将请求发送到spring.春天把它寄给了杰克逊.杰克逊被NIO阻止等待更多数据进入.

Thread 12128: (state = BLOCKED)
 - sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
 - java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=20, line=226 (Compiled frame)
 ...
 - org.apache.tomcat.util.net.NioEndpoint$KeyAttachment.awaitLatch(java.util.concurrent.CountDownLatch, long, java.util.concurrent.TimeUnit) @bci=18, line=1582 (Compiled frame)
 ...
 - org.apache.tomcat.util.net.NioSelectorPool.read(java.nio.ByteBuffer, org.apache.tomcat.util.net.NioChannel, java.nio.channels.Selector, long) @bci=7, line=227 (Compiled frame)
 - org.apache.coyote.http11.InternalNioInputBuffer.readSocket(boolean, boolean) @bci=103, line=427 (Compiled frame)
...
 - org.apache.catalina.connector.CoyoteInputStream.read(byte[], int, int) @bci=76, line=200 (Compiled frame)
 - com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper.ensureLoaded(int) @bci=49, line=503 (Compiled frame)
 ...
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.io.InputStream, com.fasterxml.jackson.databind.JavaType) @bci=6, line=2158 (Compiled frame)
 - org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.readJavaType(com.fasterxml.jackson.databind.JavaType, org.springframework.http.HttpInputMessage) @bci=11, line=225 (Compiled frame)
 ...
 - org.springframework.web.servlet.FrameworkServlet.doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) @bci=3, line=863 (Compiled frame)
 - javax.servlet.http.HttpServlet.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) @bci=149, line=646 (Compiled frame)
 - org.springframework.web.servlet.FrameworkServlet.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) @bci=32, line=837 (Compiled frame)
 - javax.servlet.http.HttpServlet.service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) @bci=30, line=727 (Compiled frame)
 - org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) @bci=446, line=303 (Compiled frame)
 ...
 - org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(java.nio.channels.SelectionKey, org.apache.tomcat.util.net.NioEndpoint$KeyAttachment) @bci=140, line=1736 (Interpreted frame)
 - org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run() @bci=94, line=1695 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=95, line=1145 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=615 (Interpreted frame)
 - org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run() @bci=4, line=61 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
Run Code Online (Sandbox Code Playgroud)

我的问题是,如果有200人这样做,那么我的线程池就会变得饥饿,合法的请求也无法进入.这似乎是针对在tomcat上运行的任何东西进行的非常简单的DOS攻击.

我认为有一种方法可以通过让NIO HTTP连接器向前读入其缓冲区来解决这个问题.如果是这样,我该如何设置呢?

即便如此,似乎恶意代理可以通过向其发送大型对象来轻松地降低服务.当慢速,错误或恶意客户端连接时,人们通常如何防止线程饥饿?

Tod*_*odd 0

检查Tomcat 中的卡住螺纹检测阀。

来自文档:

该阀允许检测需要很长时间才能处理的请求,这可能表明正在处理该请求的线程被卡住。此外,它可以选择中断此类线程以尝试解锁它们。

当检测到此类请求时,其线程的当前堆栈跟踪将以 WARN 级别写入 Tomcat 日志。

您可以指定请求所需的秒数,最多(默认 10 分钟!),如果超过该值,此阀将杀死它们。

  • 如果这是最好的方法那就太可惜了——它看起来相当脆弱。我真的希望有一种方法可以将一堆 IO 推送到 NIO 选择器线程上。 (2认同)