Tomcat怎麼實現異步Servlet

有時Servlet在生成響應報文前必須等待某些耗時的操做,好比在等待一個可用的JDBC鏈接或等待一個遠程Web服務的響應。對於這種狀況servlet規範中定義了異步處理方式,因爲Servlet中等待阻塞會致使Web容器總體的處理能力低下,因此對於比較耗時的操做能夠放置到另一個線程中進行處理,此過程保留鏈接的請求和響應對象,在處理完成以後能夠把處理的結果通知到客戶端。javascript

下面先看Servlet在同步狀況下的處理過程,如圖所示,Tomcat的客戶端請求由管道處理最後會經過Wrapper容器的管道,這時它會調Servlet實例的service方法進行邏輯處理,處理完後響應客戶端,整個處理由Tomcat的Executor線程池的線程處理,而線程池的最大線程數使有限制的,因此這個處理過程越短、越快把線程讓回線程池就越好。但若是Servlet中的處理邏輯耗時越長就會致使長期地佔用Tomcat的處理線程池,影響Tomcat的總體處理能力。java

Servlet同步處理

爲了解決上面的問題引入了支持異步的Servlet,一樣是客戶端請求到來,而後經過管道最後進入到Wrapper容器的管道,調用Servlet實例的service後,建立一個異步上下文將耗時的邏輯操做封裝起來,交給用戶本身定義的線程池,這時Tomcat的處理線程就能立刻回到Executor線程池,而不用等待耗時的操做完成才讓出線程,從而提高了Tomcat的總體處理能力。這裏要注意的是,因爲後面作完耗時的操做後還須要對客戶端響應,因此須要保持住Request和Response對象,以便輸出響應報文到客戶端。app

Servlet異步處理

再結合一個簡單的異步代碼來看Tomcat對Servlet異步的實現:異步

public class AsyncServlet extends HttpServlet {

    ScheduledThreadPoolExecutor userExecutor = new ScheduledThreadPoolExecutor(5);

    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        AsyncContext aCtx = req.startAsync(req, res);
        userExecutor.execute(new AsyncHandler(aCtx));
    }

}

public class AsyncHandler implements Runnable {

    private AsyncContext ctx;

    public AsyncHandler(AsyncContext ctx) {
        this.ctx = ctx;
    }

    @Override
    public void run() {
        //耗時操做
        PrintWriter pw;
        try {
            pw = ctx.getResponse().getWriter();
            pw.print("done!");
            pw.flush();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        ctx.complete();
    }
}複製代碼

咱們建立一個AsyncServlet,它定義了一個userExecutor線程池專門用於處理該Servlet的全部請求的耗時的邏輯操做。這樣就不會佔用Tomcat內部的Executor線程池,影響到對其餘Servlet的處理。這種思想有點像資源隔離,耗時的操做統一由指定的線程池處理,而不要影響其它耗時少的請求處理。ide

Servlet的異步的實現就很好理解了,startAsync方法其實就是建立了一個異步上下文AsyncContext對象,該對象封裝了請求和響應對象。而後建立一個任務用於處理耗時邏輯,後面經過AsyncContext對象得到響應對象並對客戶端響應,輸出「done!」。完成後要經過complete方法告訴Tomcat內部我已經處理完,Tomcat就會請求對象和響應對象進行回收處理或關閉鏈接。this

歡迎關注:spa

相關文章
相關標籤/搜索