Github地址html
相关系列文章:java
Servlet Async Processing提供了一种异步请求处理的手段(见个人另外一篇文章Servlet 3.0 异步处理详解),可以让你将Http thread从慢速处理中释放出来出来其余请求,提升系统的响应度。git
可是光有Async Processing是不够的,由于整个请求-响应过程的速度快慢还牵涉到了客户端的网络状况,若是客户端网络状况糟糕,其上传和下载速度都很慢,那么一样也会长时间占用Http Thread使其不能被释放出来。github
因而Servlet 3.1提供了Async IO机制,使得从Request中读、往Response里写变成异步动做。web
咱们先来一段客户端上传速度慢的例子,AsyncReadServlet.java:apache
@WebServlet(value = "/async-read", asyncSupported = true) public class AsyncReadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Servlet thread: " + Thread.currentThread().getName()); AsyncContext asyncCtx = req.startAsync(); ServletInputStream is = req.getInputStream(); is.setReadListener(new ReadListener() { private int totalReadBytes = 0; @Override public void onDataAvailable() { System.out.println("ReadListener thread: " + Thread.currentThread().getName()); try { byte buffer[] = new byte[1 * 1024]; int readBytes = 0; while (is.isReady() && !is.isFinished()) { int length = is.read(buffer); if (length == -1 && is.isFinished()) { asyncCtx.complete(); System.out.println("Read: " + readBytes + " bytes"); System.out.println("Total Read: " + totalReadBytes + " bytes"); return; } readBytes += length; totalReadBytes += length; } System.out.println("Read: " + readBytes + " bytes"); } catch (IOException ex) { ex.printStackTrace(); asyncCtx.complete(); } } @Override public void onAllDataRead() { try { System.out.println("Total Read: " + totalReadBytes + " bytes"); asyncCtx.getResponse().getWriter().println("Finished"); } catch (IOException ex) { ex.printStackTrace(); } asyncCtx.complete(); } @Override public void onError(Throwable t) { System.out.println(ExceptionUtils.getStackTrace(t)); asyncCtx.complete(); } }); } }
咱们利用curl
的--limit-rate
选项来模拟慢速上传curl -X POST -F "bigfile=@src/main/resources/bigfile" --limit-rate 5k http://localhost:8080/async-read
segmentfault
而后观察服务端的打印输出:网络
Servlet thread: http-nio-8080-exec-3 ReadListener thread: http-nio-8080-exec-3 Read: 16538 bytes ReadListener thread: http-nio-8080-exec-4 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-5 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-7 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-6 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-8 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-9 Read: 16384 bytes ReadListener thread: http-nio-8080-exec-10 Read: 2312 bytes ReadListener thread: http-nio-8080-exec-1 Read: 48 bytes Total Read: 117202 bytes
能够从输出看到除了doGet和第一次进入onDataAvailable是同一个Http thread以外,后面的read动做都发生在另外的Http thread里。
这是由于客户端的数据推送速度太慢了,容器先将Http thread收回,当容器发现能够读取到新数据的时候,再分配一个Http thread去读InputStream,如此循环直到所有读完为止。oracle
注意:HttpServletRequest.getInputStream()
和getParameter*()
不能同时使用。app
再来一段客户端下载慢的例子,AsyncWriteServlet.java:
@WebServlet(value = "/async-write", asyncSupported = true) public class AsyncWriteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("Servlet thread: " + Thread.currentThread().getName()); AsyncContext asyncCtx = req.startAsync(); ServletOutputStream os = resp.getOutputStream(); InputStream bigfileInputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("bigfile"); os.setWriteListener(new WriteListener() { @Override public void onWritePossible() throws IOException { int loopCount = 0; System.out.println("WriteListener thread: " + Thread.currentThread().getName()); while (os.isReady()) { loopCount++; System.out.println("Loop Count: " + loopCount); byte[] bytes = readContent(); if (bytes != null) { os.write(bytes); } else { closeInputStream(); asyncCtx.complete(); break; } } } @Override public void onError(Throwable t) { try { os.print("Error happened"); os.print(ExceptionUtils.getStackTrace(t)); } catch (IOException e) { e.printStackTrace(); } finally { closeInputStream(); asyncCtx.complete(); } } private byte[] readContent() throws IOException { byte[] bytes = new byte[1024]; int readLength = IOUtils.read(bigfileInputStream, bytes); if (readLength <= 0) { return null; } return bytes; } private void closeInputStream() { IOUtils.closeQuietly(bigfileInputStream); } }); } }
一样利用curl
作慢速下载,curl --limit-rate 5k http://localhost:8080/async-write
接下来看如下服务端打印输出:
Servlet thread: http-nio-8080-exec-1 WriteListener thread: http-nio-8080-exec-1 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-2 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-3 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-4 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-5 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-6 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-7 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-8 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-9 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-10 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-1 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-2 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-3 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-4 Write bytes: 8192 WriteListener thread: http-nio-8080-exec-5 Write bytes: 2312
PS. 后发现即便没有添加--limit-rate
参数,也会出现相似于上面的结果。
上面两个例子使用的是curl
来模拟,咱们也提供了Jmeter的benchmark。
须要注意的是,必须在user.properties文件所在目录启动Jmeter,由于这个文件里提供了模拟慢速链接的参数httpclient.socket.http.cps=5120
。而后利用Jmeter打开benchmark.xml。