Servlet 3之非同步請求處理
簡介
像Tomcat這樣的Servlet容器,每個請求需要佔用它一個執行緒,直到該請求被處理完才釋放。對於那些處理時間長,而且大部分是IO類操作的請求,這樣做會很浪費,因為處理它們的容器執行緒會花很長時間進行等待,卻不能為其他請求服務。如果有大量這樣的請求,容器的執行緒就會很快被佔滿,導致其他請求被迫等待甚至超時。
於是Servlet3 添加了非同步請求處理(asynchronous request processing)的新特性,有了它我們就可以把那些處理時間長的IO類請求丟給後臺執行緒去執行,從而可以快速釋放容器執行緒用以服務更多的新請求。這樣做可以大大提升我們服務的效能(吞吐、時延和併發上都有改進)。下面將詳細介紹怎麼使用這個新特性。
Servlet 3之Async Support
首先看看直接寫個servlet如何使用這個非同步處理功能:
第一步,需要把你的servlet宣告為syncSupport,servlet3 提供了註解的方式,也可以使用web.xml配置的方式。
第二步,實現你的Servlet 的service方法:
- 呼叫request.startAsync()開啟非同步模式,該方法將返回AsyncContext物件用於後臺執行緒非同步處理該請求。後臺執行緒可使用AsyncContext的getRequest()和getResponse()方法來讀取請求和寫Response,處理完成後呼叫AsyncContext.complete()結束請求處理。
- service方法可以退出了。(servlet 3還支援你將請求非同步的dispatch到另一個請求地址,這裡將不做介紹)
示例程式碼:
/*asyncSupported is a boolean with a default value of false. When asyncSupported is set to true the application can start asynchronous processing in a separate thread by calling startAsync */ @WebServlet(value = "/async-servlet", asyncSupported = true) public class AsyncServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(this.getClass()); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("Entering async servlet..."); // Start async mode, this ensures that the response isn't committed when // the application exits out of the service method AsyncContext asyncContext = request.startAsync(); new Thread(() -> { //...your long IO operations asyncContext.getResponse().getWriter().write("Async Return. "); // write to response asyncContext.complete(); // complete the async processing and commit and close the response }, "async-handling").start(); logger.info("Exit async servlet."); } }
Spring MVC之Async Support
接下來看看如果是寫個Spring的Controller該如何進行非同步處理:
第一步,全域性開啟async-support,如果使用Spring Boot則預設就幫你開啟了,如果使用外部容器(Tomcat)那麼你還需要配置它(在web.xml裡面或者使用Spring 提供的WebApplicationInitializer來配置)
第二步,實現返回值是DeferredResult<?>或者Callable<?>的Controller方法。如果用DeferredResult,則需要將它傳給你的後臺執行緒,這樣後臺執行緒可以呼叫它的setResult方法來寫Response;如果用Callable,則在Callable裡面寫你的後臺處理程式碼,並返回response。
示例程式碼:
/**
* AbstractAnnotationConfigDispatcherServletInitializer will turn on async-support by default
*
*/
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { ControllerConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
}
@RestController
@RequestMapping("/async")
public class AsyncController {
@GetMapping("/deferred")
@ResponseBody
public DeferredResult<String> getDeferred() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
new Thread(() -> {
try {
//... Your long-run IO operations goes here
deferredResult.setResult("Async Return. "); //write the response
} catch (Exception e) {
deferredResult.setErrorResult(e); //write error response
}
}, "async-handling").start();
return deferredResult;
}
@GetMapping("/callable")
@ResponseBody
public Callable<String> getCallable() {
//This will be called by AsyncTaskExecutor
Callable<String> callable = () -> {
//... Your long-run IO operations goes here
return "Async Return";//write the response
};
return callable;
}
}
Spring 有個@Async註解可以用來實現非同步方法,如果把後臺處理邏輯寫在一個@Async方法裡面,Controller就可以這麼寫:
@Service
public class AsyncService {
/**
* Calls to @Async method will return immediately, and AsyncTaskExecutor will execute this method and return the result in the future.
*/
@Async
public CompletableFuture<String> asyncCall() {
//... your long-run IO operations goes here
return CompletableFuture.completedFuture("Async Return. "); // write the result
}
}
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/deferred")
@ResponseBody
public DeferredResult<String> getDeferred() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
asyncService.asyncCall().thenApply(result -> {
deferredResult.setResult(result);
return null;
});
return deferredResult;
}
}