1. 程式人生 > >Servlet 3之非同步請求處理

Servlet 3之非同步請求處理

簡介

像Tomcat這樣的Servlet容器,每個請求需要佔用它一個執行緒,直到該請求被處理完才釋放。對於那些處理時間長,而且大部分是IO類操作的請求,這樣做會很浪費,因為處理它們的容器執行緒會花很長時間進行等待,卻不能為其他請求服務。如果有大量這樣的請求,容器的執行緒就會很快被佔滿,導致其他請求被迫等待甚至超時。

於是Servlet3 添加了非同步請求處理(asynchronous request processing)的新特性,有了它我們就可以把那些處理時間長的IO類請求丟給後臺執行緒去執行,從而可以快速釋放容器執行緒用以服務更多的新請求。這樣做可以大大提升我們服務的效能(吞吐、時延和併發上都有改進)。下面將詳細介紹怎麼使用這個新特性。

Servlet 3之Async Support

首先看看直接寫個servlet如何使用這個非同步處理功能:

第一步,需要把你的servlet宣告為syncSupport,servlet3 提供了註解的方式,也可以使用web.xml配置的方式。

第二步,實現你的Servlet 的service方法:

  1. 呼叫request.startAsync()開啟非同步模式,該方法將返回AsyncContext物件用於後臺執行緒非同步處理該請求。後臺執行緒可使用AsyncContext的getRequest()和getResponse()方法來讀取請求和寫Response,處理完成後呼叫AsyncContext.complete()結束請求處理。
  2. 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;
    }
}

參考資料