非同步Servlet
有的時候servlet在相應報文之前會有一些耗時操作,比如JDBC的操作,或者等待另一個遠端Web的響應,同步Servlet中等待阻塞會導致Web容器整體的處理能力低下。對於這種情況可使用servlet非同步處理方式,把比較耗時的操作可以放置到另外一個執行緒中進行處理,此過程保留連線的請求和響應物件,在處理完成之後可以把處理的結果通知到客戶端。
同步Servlet
如圖所示,Tomcat的客戶端請求由管道處理最後會通過Wrapper容器的管道,這時它會調Servlet例項的service方法進行邏輯處理,處理完後響應客戶端,整個處理由Tomcat的Executor執行緒池的執行緒處理。
![img](file:///var/folders/74/8gxv5r0j3xvflks6fxth63540000gn/T/WizNote/de95c2ac-493a-4c0b-ac87-f45447bb6345/index_files/73010586.png)
缺點:遇見處理邏輯耗時較長的任務會長時間的佔用tomcat的處理執行緒,影響tomcat的整體處理能力。特別是當你的執行緒數有限比如只有5個,同時遇見5個耗時100ms的請求,那麼在這100ms內的其它請求都會失敗,嚴重影響的服務的吞吐量和穩定性。
優點:實現邏輯簡單易懂,對於大量的耗時短的任務效能好。
非同步Servlet
非同步Servlet是在3.0版本引入的,客戶端請求到來,然後通過管道最後進入到Wrapper容器的管道,呼叫Servlet例項的service後,建立一個非同步上下文將耗時的邏輯操作封裝起來,交給使用者自己定義的執行緒池,這時Tomcat的處理執行緒就能馬上回到Executor執行緒池,而不用等待耗時的操作完成才讓出執行緒,從而提升了Tomcat的整體處理能力。
![img](file:///var/folders/74/8gxv5r0j3xvflks6fxth63540000gn/T/WizNote/de95c2ac-493a-4c0b-ac87-f45447bb6345/index_files/73445233.png)
一個?
初始化執行緒池
@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();
}
}
複製程式碼
編寫一個監聽器,監聽非同步任務生命週期中的一些動作。
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");
}
}
複製程式碼
具體的業務邏輯放在一個Runable的實現類中
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();
}
}
}
複製程式碼
最後,Async Servlet 實現
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
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);
ThreadPoolExecutor executor = (ThreadPoolExecutor) request
.getServletContext().getAttribute("executor");
executor.execute(new AsyncRequestProcessor(asyncCtx));
long endTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
}
複製程式碼
PS:在 SpringBootApplication 上使用@ServletComponentScan 註解後,Servlet、Filter、Listener 可以直接通過 @WebServlet、@WebFilter、@WebListener 註解自動註冊,無需其他程式碼。