在Servlet 3.1中Non-blocking IO
jopen
10年前
Servlet 3.0 中引入了Async Servlet,然而虽然servlet 的执行过程可以是异步的了,但是从request 读取和向response 写入的过程,依然是同步的IO。鉴于此,Async Servlet 对于长连接这样的场景是很适合的,但是对于qps 很高的场景还是无能为例,read 和 write 的过程仍然会占用线程池中的执行时间按。
Servlet 3.1 在Async Servlet 的基础上,引入了Non-blocking IO Servlet,IO 的过程是通过WriteListener 和 ReadListener来实现的,类似于event driven 的过程。
为InputStream 设置一个ReadListener,这样有数据可以读得时候会被回调:
@WebServlet(urlPatterns="/test", asyncSupported=true) public class TestServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // start async AsyncContext ac = req.startAsync(); // set up async listener ac.addListener(new AsyncListener() { public void onComplete(AsyncEvent event) throws IOException { System.out.println("Complete"); } public void onError(AsyncEvent event) { System.out.println(event.getThrowable()); } public void onStartAsync(AsyncEvent event) { } public void onTimeout(AsyncEvent event) { System.out.println("my asyncListener.onTimeout"); } }); // set up ReadListener to read data for processing ServletInputStream input = req.getInputStream(); ReadListener readListener = new ReadListenerImpl(input, res, ac); input.setReadListener(readListener); } }
ReadListener 的实现,在数据读完之后(onAllDataRead),为out 设置一个write listener,在可以写入的时候会被回调:
class ReadListenerImpl implements ReadListener { private ServletInputStream input = null; private HttpServletResponse res = null; private AsyncContext ac = null; // store the processed data to be sent back to client later private Queue queue = new LinkedBlockingQueue(); ReadListenerImpl(ServletInputStream in, HttpServletResponse r, AsyncContext c) { input = in; res = r; ac = c; } public void onDataAvailable() throws IOException { StringBuilder sb = new StringBuilder(); int len = -1; byte b[] = new byte[1024]; // We need to check input#isReady before reading data. // The ReadListener will be invoked again when // the input#isReady is changed from false to true while (input.isReady() && (len = input.read(b)) != -1) { String data = new String(b, 0, len); sb.append(data); } queue.add(sb.toString()); } public void onAllDataRead() throws IOException { // now all data are read, set up a WriteListener to write ServletOutputStream output = res.getOutputStream(); WriteListener writeListener = new WriteListenerImpl(output, queue, ac); output.setWriteListener(writeListener); } public void onError(final Throwable t) { ac.complete(); t.printStackTrace(); } }
class WriteListenerImpl implements WriteListener { private ServletOutputStream output = null; private Queue queue = null; private AsyncContext ac = null; WriteListenerImpl(ServletOutputStream sos, Queue q, AsyncContext c) { output = sos; queue = q; ac = c; } public void onWritePossible() throws IOException { // write while there is data and is ready to write while (queue.peek() != null && output.isReady()) { String data = queue.poll(); output.print(data); } // complete the async process when there is no more data to write if (queue.peek() == null) { ac.complete(); } } public void onError(final Throwable t) { ac.complete(); t.printStackTrace(); } }
Non-blocking IO Servlet 的一个瑕疵,所有的write 和 read 接口都是使用的byte[] 而不是bytebuffer,以至于无法实现zero copy。接口设计上亦比较底层,若想较方便的使用还需要等待Spring 等其他框架的支持了。
</div>