溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

淺談servlet3異步原理與實(shí)踐

發(fā)布時(shí)間:2020-09-28 09:30:13 來源:腳本之家 閱讀:194 作者:新棟BOOK 欄目:編程語言

一、什么是Servlet

servlet 是基于 Java 的 Web 組件,由容器進(jìn)行管理,來生成動(dòng)態(tài)內(nèi)容。像其他基于 Java 的組件技術(shù)一樣,servlet 也是基于平臺(tái)無關(guān)的 Java 類格式,被編譯為平臺(tái)無關(guān)的字節(jié)碼,可以被基于 Java 技術(shù)的 Web 服務(wù)器動(dòng)態(tài)加載并運(yùn)行。容器(Container),有時(shí)候也叫做 servlet 引擎,是 Web 服務(wù)器為支持 servlet 功能擴(kuò)展的部分。客戶端通過 servlet 容器實(shí)現(xiàn)的 request/response paradigm(請求/應(yīng)答模式) 與 Servlet 進(jìn)行交互。

二、什么是Servlet規(guī)范

每當(dāng)一個(gè)Servlet版本發(fā)布都會(huì)對(duì)應(yīng)一個(gè)Servlet版本的規(guī)范,比如Servlet2.5、Servlet3.0、Servlet3.1.
規(guī)范中描述了Java Servlet API 的標(biāo)準(zhǔn),定義了 Java Servlet API 中類、接口、方法簽名的完整規(guī)范且附帶的Javadoc 文檔供開發(fā)人員查閱,目的主要是為Java Servlet 給出一個(gè)完整和清晰的解釋。從下圖可以看出Servlet規(guī)范版本和tomcat支持的版本的對(duì)應(yīng)關(guān)系。比如Servlet3是從tomcat7以后開始支持的。

淺談servlet3異步原理與實(shí)踐

Servlet和tomcat版本.png

三、同步,異步,阻塞,非阻塞

同步異步是數(shù)據(jù)通信的方式,阻塞和非阻塞是一種狀態(tài)。比如同步這種數(shù)據(jù)通訊方式里面可以有阻塞狀態(tài)也可以有非阻塞狀態(tài)。

四、Servlet3的異步位置

這里說的位置是指,從tomcat處理整個(gè)request請求流程中,異步處于哪一步。我們先梳理出在NIO模式下(是否使用NIO跟異步?jīng)]有直接關(guān)系,這里是拿NIO模式下的tomcat流程做說明),下面這個(gè)圖是tomcat的總體結(jié)構(gòu),里面用箭頭標(biāo)明了請求線路。

淺談servlet3異步原理與實(shí)踐

tomcat架構(gòu)圖.png

我們知道在tomcat的組件中Connector和Engine是最核心的兩個(gè)組件,Servlet3的異步處理就是發(fā)生在Connector中。Tomcat的組件之間的協(xié)作關(guān)系,后續(xù)會(huì)單獨(dú)寫一篇文章介紹。這里先有一個(gè)直觀的認(rèn)識(shí)。便與后續(xù)對(duì)異步理解。

五、Servlet3的異步流程

淺談servlet3異步原理與實(shí)踐

Servlet異步處理流程圖.png

接收到request請求之后,由tomcat工作線程從HttpServletRequest中獲得一個(gè)異步上下文AsyncContext對(duì)象,然后由tomcat工作線程把AsyncContext對(duì)象傳遞給業(yè)務(wù)處理線程,同時(shí)tomcat工作線程歸還到工作線程池,這一步就是異步開始。在業(yè)務(wù)處理線程中完成業(yè)務(wù)邏輯的處理,生成response返回給客戶端。在Servlet3.0中雖然處理請求可以實(shí)現(xiàn)異步,但是InputStream和OutputStream的IO操作還是阻塞的,當(dāng)數(shù)據(jù)量大的request body 或者 response body的時(shí)候,就會(huì)導(dǎo)致不必要的等待。從Servlet3.1以后增加了非阻塞IO,需要tomcat8.x支持。

六、Servlet3的異步使用步驟

我們使用的大致步驟如下:

1、聲明Servlet,增加asyncSupported屬性,開啟異步支持。@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
2、通過request獲取異步上下文AsyncContext。AsyncContext asyncCtx = request.startAsync();
3、開啟業(yè)務(wù)邏輯處理線程,并將AsyncContext 傳遞給業(yè)務(wù)線程。executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
4、在異步業(yè)務(wù)邏輯處理線程中,通過asyncContext獲取request和response,處理對(duì)應(yīng)的業(yè)務(wù)。
5、業(yè)務(wù)邏輯處理線程處理完成邏輯之后,調(diào)用AsyncContext 的complete方法。asyncContext.complete();從而結(jié)束該次異步線程處理。

七、Servlet3的異步使用示例

7.1、AsyncLongRunningServlet.java 處理Servlet請求,并開啟異步

package com.test.servlet3;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by wangxindong on 2017/10/19.
 */
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    System.out.println("AsyncLongRunningServlet Start::Name="
        + Thread.currentThread().getName() + "::ID="
        + Thread.currentThread().getId());

    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

    String time = request.getParameter("time");
    int secs = Integer.valueOf(time);
    // max 10 seconds
    if (secs > 10000)
      secs = 10000;

    AsyncContext asyncCtx = request.startAsync();
    asyncCtx.addListener(new AppAsyncListener());
    asyncCtx.setTimeout(9000);//異步servlet的超時(shí)時(shí)間,異步Servlet有對(duì)應(yīng)的超時(shí)時(shí)間,如果在指定的時(shí)間內(nèi)沒有執(zhí)行完操作,response依然會(huì)走原來Servlet的結(jié)束邏輯,后續(xù)的異步操作執(zhí)行完再寫回的時(shí)候,可能會(huì)遇到異常。

    ThreadPoolExecutor executor = (ThreadPoolExecutor) request
        .getServletContext().getAttribute("executor");

    executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
    long endTime = System.currentTimeMillis();
    System.out.println("AsyncLongRunningServlet End::Name="
        + Thread.currentThread().getName() + "::ID="
        + Thread.currentThread().getId() + "::Time Taken="
        + (endTime - startTime) + " ms.");
  }
}

7.2、AppAsyncListener.java 異步監(jiān)聽器

package com.test.servlet3;

import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/19.
 */
@WebListener
public class AppAsyncListener implements AsyncListener {
  @Override
  public void onComplete(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onComplete");
    // we can do resource cleanup activity here
  }

  @Override
  public void onError(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onError");
    //we can return error response to client
  }

  @Override
  public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onStartAsync");
    //we can log the event here
  }

  @Override
  public void onTimeout(AsyncEvent asyncEvent) throws IOException {
    System.out.println("AppAsyncListener onTimeout");
    //we can send appropriate response to client
    ServletResponse response = asyncEvent.getAsyncContext().getResponse();
    PrintWriter out = response.getWriter();
    out.write("TimeOut Error in Processing");
  }
}

7.3、AppContextListener.java Servlet上下文監(jiān)聽器,可以在里面初始化業(yè)務(wù)線程池

package com.test.servlet3;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by wangxindong on 2017/10/19.
 * 在監(jiān)聽中初始化線程池
 */
@WebListener
public class AppContextListener implements ServletContextListener {
  public void contextInitialized(ServletContextEvent servletContextEvent) {

    // create the thread pool
    ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
        TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
    servletContextEvent.getServletContext().setAttribute("executor",
        executor);

  }

  public void contextDestroyed(ServletContextEvent servletContextEvent) {
    ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
        .getServletContext().getAttribute("executor");
    executor.shutdown();
  }
}

7.4、AsyncRequestProcessor.java 業(yè)務(wù)工作線程

package com.test.servlet3;

import javax.servlet.AsyncContext;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/19.
 * 業(yè)務(wù)工作線程
 */
public class AsyncRequestProcessor implements Runnable {
  private AsyncContext asyncContext;
  private int secs;

  public AsyncRequestProcessor() {
  }

  public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
    this.asyncContext = asyncCtx;
    this.secs = secs;
  }

  @Override
  public void run() {
    System.out.println("Async Supported? "
        + asyncContext.getRequest().isAsyncSupported());
    longProcessing(secs);
    try {
      PrintWriter out = asyncContext.getResponse().getWriter();
      out.write("Processing done for " + secs + " milliseconds!!");
    } catch (IOException e) {
      e.printStackTrace();
    }
    //complete the processing
    asyncContext.complete();
  }

  private void longProcessing(int secs) {
    // wait for given time before finishing
    try {
      Thread.sleep(secs);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

八、Tomcat NIO Connector ,Servlet 3.0 Async,Spring MVC Async的關(guān)系

對(duì)于這幾個(gè)概念往往會(huì)混淆,這里做一個(gè)梳理比較,nio是一種IO的模型,對(duì)比與傳統(tǒng)的BIO,它可以利用較少的線程處理更多的連接從而增加機(jī)器的吞吐量,Tomcat NIO Connector是Tomcat的一種NIO連接模式。異步,前面提到他是一種通訊的方式,它跟NIO沒有任務(wù)關(guān)系,及時(shí)沒有NIO也可以實(shí)現(xiàn)異步,Servlet 3.0 Async是指Servlet 3規(guī)范以后支持了異步處理Servlet請求,我們可以把請求線程和業(yè)務(wù)線程分開。Spring MVC Async是在Servlet3異步的基礎(chǔ)上做了一層封裝。具體的區(qū)別如下:

8.1、Tomcat NIO Connector

Tomcat的Connector 有三種模式,BIO,NIO,APR,Tomcat NIO Connector是其中的NIO模式,使得tomcat容器可以用較少的線程處理大量的連接請求,不再是傳統(tǒng)的一請求一線程模式。Tomcat的server.xml配置protocol="org.apache.coyote.http11.Http11NioProtocol",Http11NioProtocol 從 tomcat 6.x 開始支持。NIO的細(xì)節(jié)可以參看NIO相關(guān)技術(shù)文章。

8.2、Servlet 3.0 Async

是說Servlet 3.0支持了業(yè)務(wù)請求的異步處理,Servlet3之前一個(gè)請求的處理流程,請求解析、READ BODY,RESPONSE BODY,以及其中的業(yè)務(wù)邏輯處理都由Tomcat線程池中的一個(gè)線程進(jìn)行處理的。那么3.0以后我們可以讓請求線程(IO線程)和業(yè)務(wù)處理線程分開,進(jìn)而對(duì)業(yè)務(wù)進(jìn)行線程池隔離。我們還可以根據(jù)業(yè)務(wù)重要性進(jìn)行業(yè)務(wù)分級(jí),然后再把線程池分級(jí)。還可以根據(jù)這些分級(jí)做其它操作比如監(jiān)控和降級(jí)處理。servlet 3.0 從 tomcat 7.x 開始支持。

8.3、Spring MVC Async

是Spring MVC 3.2 以上版本基于Servlet 3的基礎(chǔ)做的封裝,原理及實(shí)現(xiàn)方式同上,使用方式如下:

@Controller
@RequestMapping("/async/TestController")
public class TestController {
  @ResponseBody
  @RequestMapping("/{testUrl}")
  public DeferredResult<ResponseEntity<String>> testProcess(@PathVariable String testUrl) {
    final DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<ResponseEntity<String>>();

    // 業(yè)務(wù)邏輯異步處理,將處理結(jié)果 set 到 DeferredResult
    new Thread(new AsyncTask(deferredResult)).start();

    return deferredResult;
  }

  private static class AsyncTask implements Runnable {

    private DeferredResult result;

    private AsyncTask(DeferredResult result) {
      this.result = result;
    }

    @Override
    public void run() {
      //業(yè)務(wù)邏輯START
      //...
      //業(yè)務(wù)邏輯END
      result.setResult(result);
    }
  }
}

九、Servlet3非阻塞IO

Servlet3.1以后增加了非阻塞IO實(shí)現(xiàn),需要Tomcat8.x以上支持。根據(jù)Servlet3.1規(guī)范中的描述”非阻塞 IO 僅對(duì)在 Servlet 中的異步處理請求有效,否則,當(dāng)調(diào)用 ServletInputStream.setReadListener 或ServletOutputStream.setWriteListener 方法時(shí)將拋出IllegalStateException“。可以說Servlet3的非阻塞IO是對(duì)Servlet3異步的增強(qiáng)。Servlet3的非阻塞是利用java.util.EventListener的事件驅(qū)動(dòng)機(jī)制來實(shí)現(xiàn)的。

9.1、AsyncLongRunningServlet.java 接收請求,獲取讀取請求監(jiān)聽器ReadListener

package com.test.servlet3Noblock;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/23.
 */
@WebServlet(urlPatterns = "/AsyncLongRunningServlet2", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
  protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    AsyncContext actx = request.startAsync();//通過request獲得AsyncContent對(duì)象

    actx.setTimeout(30*3000);//設(shè)置異步調(diào)用超時(shí)時(shí)長

    ServletInputStream in = request.getInputStream();
    //異步讀?。▽?shí)現(xiàn)了非阻塞式讀取)
    in.setReadListener(new MyReadListener(in,actx));
    //直接輸出到頁面的內(nèi)容(不等異步完成就直接給頁面)
    PrintWriter out = response.getWriter();
    out.println("<h2>直接返回頁面,不等異步處理結(jié)果了</h2>");
    out.flush();
  }

}

9.2、MyReadListener.java 異步處理

package com.test.servlet3Noblock;

import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by wangxindong on 2017/10/23.
 */
public class MyReadListener implements ReadListener {
  private ServletInputStream inputStream;
  private AsyncContext asyncContext;
  public MyReadListener(ServletInputStream input,AsyncContext context){
    this.inputStream = input;
    this.asyncContext = context;
  }
  //數(shù)據(jù)可用時(shí)觸發(fā)執(zhí)行
  @Override
  public void onDataAvailable() throws IOException {
    System.out.println("數(shù)據(jù)可用時(shí)觸發(fā)執(zhí)行");
  }

  //數(shù)據(jù)讀完時(shí)觸發(fā)調(diào)用
  @Override
  public void onAllDataRead() throws IOException {
    try {
      Thread.sleep(3000);//暫停5秒,模擬耗時(shí)處理數(shù)據(jù)
      PrintWriter out = asyncContext.getResponse().getWriter();
      out.write("數(shù)據(jù)讀完了");
      out.flush();
      System.out.println("數(shù)據(jù)讀完了");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

  }

  //數(shù)據(jù)出錯(cuò)觸發(fā)調(diào)用
  @Override
  public void onError(Throwable t){
    System.out.println("數(shù)據(jù) 出錯(cuò)");
    t.printStackTrace();
  }
}

十、總結(jié)

通訊模型中的NIO可以利用很少的線程處理大量的連接,提高了機(jī)器的吞吐量。Servlet的異步處理機(jī)制使得我們可以將請求異步到獨(dú)立的業(yè)務(wù)線程去執(zhí)行,使得我們能夠?qū)⒄埱缶€程和業(yè)務(wù)線程分離。通訊模型的NIO跟Servlet3的異步?jīng)]有直接關(guān)系。但是我們將兩種技術(shù)同時(shí)使用就更增加了以tomcat為容器的系統(tǒng)的處理能力。自從Servlet3.1以后增加了非阻塞的IO,這里的非阻塞IO是面向inputstream和outputstream流,通過jdk的事件驅(qū)動(dòng)模型來實(shí)現(xiàn),更一步增強(qiáng)了Servlet異步的高性能,可以認(rèn)為是一種增強(qiáng)版的異步機(jī)制。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI