Tôi có một ứng dụng khởi động mùa xuân đơn giản. Nó có một điểm cuối duy nhất lấy một đối tượng từ thân yêu cầu và không làm gì:Tránh nạn đói do khách hàng chậm trong tomcat/spring-boot
@Controller
class FooController {
@RequestMapping(method=RequestMethod.POST, value="/foo")
public void postFoo(@RequestBody Foo foo) {
}
}
Những thứ khá đơn giản.
Sau đó tôi kết nối qua telnet và gửi qua các tiêu đề thích hợp như thể tôi sắp gửi một đối tượng mã hóa json, nhưng không bao giờ gửi yêu cầu - tôi chỉ để kết nối treo.
Chạy jstack
, tôi có thể thấy rằng tomcat đã gửi yêu cầu đến mùa xuân. Mùa xuân đã gửi nó đến jackson. Jackson bị chặn trên NIO đang chờ nhiều dữ liệu hơn.
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)
Vấn đề của tôi là nếu 200 người làm điều đó, thì hồ bơi của tôi bị bỏ đói và yêu cầu hợp pháp không thể đến. Có vẻ như đây là một cuộc tấn công DOS khá đơn giản chống lại bất cứ thứ gì chạy trên tomcat.
Tôi đoán có một cách để giải quyết vấn đề này bằng cách nhận Trình kết nối HTTP NIO để đọc trước bộ đệm của nó. Nếu vậy, làm thế nào tôi sẽ đi về việc thiết lập này?
Thậm chí sau đó, có vẻ như một tác nhân độc hại có thể dễ dàng mang dịch vụ xuống bằng cách chỉ gửi các đối tượng lớn đến đó. Làm thế nào để mọi người bình thường ngăn chặn đói đói khi khách hàng chậm, lỗi hoặc độc hại đang kết nối?
Martin, tôi cần bạn đăng thêm ngăn xếp. Tôi có cảm giác như sợi chỉ bị khóa trên CountdownLatch đó hai lần. Tất cả các hoạt động đọc/ghi từ các ổ cắm không chặn là ... không chặn. Có nghĩa là một read() sẽ trả về dữ liệu hiện có sẵn và không chờ đợi nữa. –
@Martin: Bạn đã nhận được giải pháp chưa? Vui lòng cung cấp giải pháp nếu có ai đó có thể giải quyết. –