Tomcat實作異步Servlet
有時Servlet在生成響應封包前需要等待某些耗時的操作,例如等待一個可用的JDBC連接配接或等待一個遠端Web服務的響應,是以會導緻Servlet中等待阻塞會導緻web整體處理能力低下,是以對于比較耗時的操作可以放置到另外一個線程中進行處理,此過程保留連接配接的請求和響應對象,在處理完成之後可以把處理的結果通知到用戶端,對于這種情況servlet規範中定義了異步處理方式。
Servlet在同步情況下的處理過程:Tomcat的用戶端請求由管道處理最後會通過Wrapper容器的管道,這時它會調Servlet執行個體的service方法進行邏輯處理,處理完後響應用戶端,整個處理由Tomcat的Executor線程池的線程處理,而線程池的最大線程數使有限制的,是以這個處理過程越短、越快把線程讓回線程池就越好。但如果Servlet中的處理邏輯耗時越長就會導緻長期地占用Tomcat的處理線程池,影響Tomcat的整體性能。
為了解決上面的問題引入了支援異步的Servlet,同樣是用戶端請求到來,然後通過管道最後進入到Wrapper容器的管道,調用Servlet執行個體的service後,建立一個異步上下文(AysncContext)将耗時的邏輯操作封裝起來,交給自己定義的線程池,這時Tomcat的處理線程就能馬上回到Executor線程池,而不用等待耗時的操作完成才讓出線程,進而提升了Tomcat的整體處理能力。這裡要注意的是,由于後面做完耗時的操作後還需要對用戶端響應,是以需要保持住Request和Response對象,以便輸出響應封包到用戶端。
Servlet異步處理
再結合一個簡單的異步代碼來看Tomcat對Servlet異步的實作:
@WebServlet(urlPatterns = “/some.do”, asyncSupported = true)
//想要實作異步的servlet需要添加WebServlet的注解,注解中添加屬性asyncSupported=true
//如果使用web.xml設定Servlet,則可以在中設定async-supported标簽為true
public class AsyncServlet extends HttpServlet {
//自定義線程池
ScheduledThreadPoolExecutor userExecutor = new ScheduledThreadPoolExecutor(2);
public void doGet(HttpServletRequest req, HttpServletResponse res) {
response.setContentType("text/html; charset=UTF8");
AsyncContext aCtx = req.startAsync(req, res);//AysncContext
userExecutor.execute(new AsyncHandler(aCtx));//執行異步處理邏輯
}
}
public class AsyncHandler implements Runnable {
private AsyncContext ctx;
public AsyncHandler(AsyncContext ctx) {
this.ctx = ctx;
}
/請求與響應對象都封裝在AsyncContext中,是以AsyncRequest建構時必須接受AsyncContext執行個體。範例中以暫停線程的方式來模拟長時間處理?,并輸出簡單的字元串作為響應文字?,最後調用AsyncContext的complete()對用戶端完成響應。/
@Override
public void run() {
//耗時操作
PrintWriter pw;
try {
Thread.sleep(1000);
pw = ctx.getResponse().getWriter();
pw.print(“done!”);
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
ctx.complete();
}
}
如果Servlet将會進行異步處理,若其前端有過濾器,則過濾器亦需标示其支援異步處理,如果使用@WebFilter,同樣可以設定其asyncSupported為true。例如:
@WebFilter(urlPatterns = “/some.do”, asyncSupported = true)
public class AsyncFilter implements Filter{
…
如果使用web.xml設定過濾器,則可以設定async-supported标簽為true:
我們建立一個AsyncServlet,它定義了一個userExecutor線程池專門用于處理該Servlet的所有請求的耗時的邏輯操作。這樣就不會占用Tomcat内部的Executor線程池,影響到對其他Servlet的處理。這種思想有點像資源隔離,耗時的操作統一由指定的線程池處理,而不要影響其它耗時少的請求處理。
Servlet的異步的實作就很好了解了,startAsync方法其實就是建立了一個異步上下文AsyncContext對象,該對象封裝了請求和響應對象。然後建立一個任務用于處理耗時邏輯,後面通過AsyncContext對象獲得響應對象并對用戶端響應,輸出“done!”。将暫緩至調用AsyncContext的complete()或dispatch()方法為止,前者表示響應完成,後者表示将調派指定的URL進行響應。