zoukankan      html  css  js  c++  java
  • 异步servlet的原理探究

    异步servlet是servlet3.0开始支持的,对于单次访问来讲,同步的servlet相比异步的servlet在响应时长上并不会带来变化(这也是常见的误区之一),但对于高并发的服务而言异步servlet能增加服务端的吞吐量。本篇来从源码角度上来探究为何说异步servlet能增加服务端的吞吐量的?

    首先来个简单的异步servlet的demo

    
    
    @WebServlet(
        name = "asynchelloServlet",
        urlPatterns = {"/asynchello"},
        asyncSupported = true
    )
    public class AsyncHelloServlet extends HttpServlet {
        private static final ThreadPoolExecutor executor;
    
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            AsyncContext ctx = req.startAsync();
            executor.execute(() -> {
                System.out.println("AsyncHello Start->" + LocalDateTime.now());
    
                try {
                    PrintWriter writer = ctx.getResponse().getWriter();
                    writer.write("asyncHelloWorld");
                } catch (IOException var2) {
                    var2.printStackTrace();
                }
    
                ctx.complete();
                System.out.println("AsyncHello End->" + LocalDateTime.now());
            });
        }
    
        static {
            executor = new ThreadPoolExecutor(10, 20, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100));
        }
    }
    
    
    

    上面的代码写异步servlet的写法最关键的就是

    • AsyncContext ctx = req.startAsync()
    • ctx.complete()

    我们先讲讲下当逻辑进入servlet之前,tomcat经历了哪些步骤:

    • tcp三次握手后
    • Acceptor线程处理 socket accept
    • Acceptor线程处理 注册registered OP_READ到多路复用器
    • ClientPoller线程 监听多路复用器的事件(OP_READ)触发
    • 从tomcat的work线程池取一个工作线程来处理socket[http-nio-8080-exec-xx],下面几个步骤也都是在work线程中进行处理的
    • 因为是http协议所以用Http11Processor来解析协议
    • CoyoteAdapter来适配包装成Request和Response对象
    • 开始走pipeline管道(Valve),最后一个invoke的是把我们的servlet对象包装的StandardWrapperValve管道

    接下来就走到我们的servlet,由于是我们是异步的servlet,

    1. req.startAsync()

       @Override
        public AsyncContext startAsync(ServletRequest request,
                ServletResponse response) {
            if (!isAsyncSupported()) {
                IllegalStateException ise =
                        new IllegalStateException(sm.getString("request.asyncNotSupported"));
                log.warn(sm.getString("coyoteRequest.noAsync",
                        StringUtils.join(getNonAsyncClassNames())), ise);
                throw ise;
            }
    
            if (asyncContext == null) {
                asyncContext = new AsyncContextImpl(this);
            }
            //修改状态机
            asyncContext.setStarted(getContext(), request, response,
                    request==getRequest() && response==getResponse().getResponse());
            asyncContext.setTimeout(getConnector().getAsyncTimeout());
    
            return asyncContext;
        }
    

    从这里开始有2个线程我们要特别关注它们分别做了哪些事情:

    • tomcat的work线程
    • 我们自定义的业务线程

    当在tomcat的work线程中调用startAsync(),会创建了一个异步的上下文(AsyncContext),并且异步的上下文(AsyncContext)会设置这个状态机状态为 STARTING, 然后把这个异步上下文放到了我们的自定义线程池中去执行,

    对于异步的servlet,有一个专门的状态机来控制:AsyncMachine,如下图

    image
    image

    那状态机的扭转控制肯定也做针对异步做了什么特殊处理

    image
    image

    这里是一个Socket状态的切换的处理逻辑,在异步servlet的时候是通过AsyncMachined的状态来连动Socket状态

    如上图异步状态机的切换过程为:

    DISPATCHED(初始)->STARTING->STARTED->COMPLETING

    Socket的状态的切换为:LONG

    对于tomcat的work线程而言,servlet调用就结束了! 正常来说,如果是同步servlet的话,request和response会在servlet执行完成后由tomcat释放掉!

    image
    image

    异步的话 在这个时机request和response肯定不能释放掉,释放那不就没得完了!

    虽然Request和Response没有释放,但是这根work线程回到tomcat的线程池中去了(非核心线程的话那就释放)。

    2. ctx.complete()

    回到我们的业务线程,处理完业务逻辑后,调用ctx.complete()

    
    @Override
    public void complete() {
        if (log.isDebugEnabled()) {
            logDebug("complete   ");
        }
        check();
        //更改异步状态机
        request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
    }
    

    注意:COMPLETING是在我们的自定义的业务线程改变的!

    修改状态会触发 新开一个tomcat工作线程 image

    异步状态状态切换:

    COMPLETING->DISPATCHED

    Socket状态切换为ASYNC_END

    如下图,一次异步的完整过程如下图:

    image
    image

    总结

    研究了整个如何异步的过程,虽然这个状态机的切换挺绕的,会发现在异步servlet中,最大的改变是为了尽快的释放tomcat的work线程,让它有机会请求新accept过来的请求,接受更多的请求,当在自定义线程池中处理好业务逻辑后,在去启动新的tomcat的work线程来处理response,这样不就很好理解了为什么说异步servlet能增加服务端的吞吐量了对吧!

    思考:

    1.  SpringBoot的@EnableAsync背后是异步servlet吗?​

    2.  servlet 3.1的non-blocking I/O 解决了3.0的什么问题?

    关注公众号一起学习


    如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。
  • 相关阅读:
    轻松学习Linux之AWK使用初步
    轻松学习Linux之理解Shell的硬链接与软连接
    轻松学习Linux之自动执行任务
    轻松学习Linux系统安装篇之fdisk命令行工具的使用
    Leetcode-1030 Next Greater Node In Linked List(链表中的下一个更大节点)
    Leetcode-1028 Convert to Base -2(负二进制转换)
    Leetcode-1029 Binary Prefix Divisible By 5(可被 5 整除的二进制前缀)
    ACM模板——2的次方表
    ACM模板——快速幂
    ACM模板——素数相关
  • 原文地址:https://www.cnblogs.com/yudongdong/p/15318065.html
Copyright © 2011-2022 走看看