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。