Servlet 3.1 Async IO分析

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

Async Read

咱們先來一段客戶端上傳速度慢的例子,AsyncReadServlet.javaapache

@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-readsegmentfault

而後觀察服務端的打印輸出:網絡

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

Async Write

再來一段客戶端下載慢的例子,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參數,也會出現相似於上面的結果。

Jmeter

上面兩個例子使用的是curl來模擬,咱們也提供了Jmeter的benchmark。

須要注意的是,必須在user.properties文件所在目錄啓動Jmeter,由於這個文件裏提供了模擬慢速鏈接的參數httpclient.socket.http.cps=5120。而後利用Jmeter打開benchmark.xml。

相關資料

相關文章
相關標籤/搜索