在Servlet 3.1中Non-blocking IO

jopen 9年前

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();      }  } 

Write Listener的实现:
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();      }  } 

注意可读或者可写回调的时候,都是放在循环里进行的: while(output.isReady()) 和while(input.isReady()),一旦变得不可能或者不可写了,就会退出循环,这次回调结束。将来变得可读或可写时,会再一次回调。

Non-blocking IO Servlet 的一个瑕疵,所有的write 和 read 接口都是使用的byte[] 而不是bytebuffer,以至于无法实现zero copy。接口设计上亦比较底层,若想较方便的使用还需要等待Spring 等其他框架的支持了。

</div>

原文链接: http://www.dongliu.net/post/5893431851220992