Servlet3 asynchronous principle

One, what is an asynchronous servlet

When a new request arrives, Tomcat will take out a thread from the thread pool to process the request, this thread will call your Web application, the Web application in the process of processing the request, the Tomcat thread will be blocked until the Web application processing The response can only be output after the completion, and finally Tomcat reclaims this thread

If your web application takes a long time to process requests (such as database queries or waiting for downstream service calls to return), then the Tomcat thread will never be recycled, which will occupy system resources and cause thread starvation in extreme cases, that is to say Tomcat has no more threads to handle new requests

How to solve this problem?

Asynchronous Servlet introduced in Servlet3.0. The main purpose is to start a separate thread in the web application to execute these time-consuming requests, and the Tomcat thread returns immediately, no longer waiting for the web application to process the request, so that the Tomcat thread can be immediately recycled to the thread pool for response Other requests reduce the resource consumption of the system and increase the throughput of the system at the same time

Two, asynchronous servlet example

Add @ServletComponentScanannotations to the SpringBoot startup class and scan Servlet

@ServletComponentScan
@SpringBootApplication
public class AsyncServletApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(AsyncServletApplication.class, args);
    }

}

Asynchronous Servlet :

@WebServlet(urlPatterns = {
    
    "/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    
    

    //Web应用线程池,用来处理异步Servlet
    ExecutorService executor = Executors.newSingleThreadExecutor();

    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) {
    
    
        //调用startAsync或者异步上下文
        AsyncContext asyncContext = req.startAsync();
        //添加AsyncListener
        asyncContext.addListener(new AsyncServletListener());
        //用线程池来执行耗时操作
        executor.execute(new Runnable() {
    
    

            @Override
            public void run() {
    
    

                //在这里做耗时的操作
                try {
    
    
                    asyncContext.getResponse().getWriter().println("Handling Async Servlet");
                } catch (IOException e) {
    
    
                }

                //异步Servlet处理完了调用异步上下文的complete方法
                asyncContext.complete();
            }

        });
    }
}

Asynchronous Servlet monitoring :

public class AsyncServletListener implements AsyncListener {
    
    
    private static final Logger LOGGER = LoggerFactory.getLogger(AsyncServletListener.class);

    /**
     * 异步线程执行完毕时回调
     *
     * @param asyncEvent
     * @throws IOException
     */
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
    
    
        LOGGER.info("AsyncServlet onComplete");
    }

    /**
     * 异步线程执行超时回调
     *
     * @param asyncEvent
     * @throws IOException
     */
    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
    
    
        LOGGER.info("AsyncServlet onTimeout");
    }

    /**
     * 异步线程执行出错回调
     *
     * @param asyncEvent
     * @throws IOException
     */
    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
    
    
        LOGGER.info("AsyncServlet onError");
    }

    /**
     * 异步线程开始执行时回调
     *
     * @param asyncEvent
     * @throws IOException
     */
    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
    
    
        LOGGER.info("AsyncServlet onStartAsync");
    }
}

The above code has three main points:

  • To register the Servlet by way of annotation, in addition to the @WebServlet annotation, you also need to add the attribute of asyncSupported=true, indicating that the current Servlet is an asynchronous Servlet

  • Web applications need to call the startAsync()methods of the Request object to get an asynchronous context AsyncContext. This context holds the request and response objects

  • The web application needs to start a new thread to handle the time-consuming operation, and the AsyncContext complete()method needs to be called after the processing is completed . The purpose is to tell Tomcat that the request has been processed

Although the asynchronous servlet allows a longer time to process the request, there is also a timeout limit. The default is 30 seconds. If the request is not processed within 30 seconds, Tomcat will trigger the timeout mechanism and return a timeout error to the browser. If your web application calls the asyncContext.complete()method again , you will get an IllegalStateException

Three, the principle of asynchronous servlet

Insert picture description here

After receiving the Request request, the Tomcat worker thread obtains an asynchronous context AsyncContext object from the HttpServletRequest, and then the Tomcat worker thread passes the AsyncContext object to the business processing thread, and the Tomcat worker thread returns to the worker thread pool. This step is the asynchronous start. Complete the business logic processing in the business processing thread, generate a response and return it to the client

Insert picture description here

In Servlet3.0, although the processing of requests can be asynchronous, the IO operations of InputStream and OutputStream are still blocked. When the Request Body or Response Body with a large amount of data occurs, it will cause unnecessary waiting. Non-blocking IO has been added since Servlet3.1, which requires Tomcat 8.x support. By adding ReadListener and WriterListener methods in HttpServletRequest and HttpServletResponse respectively, the follow-up is only performed when the IO data meets certain conditions (such as when the data is ready) operating

@WebServlet(urlPatterns = {
    
    "/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
    
    

    //Web应用线程池,用来处理异步Servlet
    ExecutorService executor = Executors.newSingleThreadExecutor();

    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    
    
        //调用startAsync或者异步上下文
        AsyncContext asyncContext = req.startAsync();
        //添加AsyncListener
        asyncContext.addListener(new AsyncServletListener());

        ServletInputStream inputStream = req.getInputStream();
        inputStream.setReadListener(new ReadListener() {
    
    
            @Override
            public void onDataAvailable() throws IOException {
    
    

            }

            @Override
            public void onAllDataRead() throws IOException {
    
    
                //用线程池来执行耗时操作
                executor.execute(new Runnable() {
    
    

                    @Override
                    public void run() {
    
    

                        //在这里做耗时的操作
                        try {
    
    
                            asyncContext.getResponse().getWriter().println("Handling Async Servlet");
                        } catch (IOException e) {
    
    
                        }

                        //异步Servlet处理完了调用异步上下文的complete方法
                        asyncContext.complete();
                    }

                });
            }

            @Override
            public void onError(Throwable throwable) {
    
    

            }
        });
    }
}

reference:

https://time.geekbang.org/column/article/106935

https://blog.csdn.net/wangxindong11/article/details/78591396

https://www.cnblogs.com/davenkin/p/async-servlet.html

Guess you like

Origin blog.csdn.net/qq_40378034/article/details/109560215