Web容器中的异步处理

作者: 右耳菌 | 来源:发表于2022-06-23 08:03 被阅读0次

    Servlet 3.x 概述

    在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行I0操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待lO操作完成,而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。

    Servlet3.x 的新特性
    • 异步处理支持:有了该特性,Servlet线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该Servlet线程。在接收到请求之后,Servlet线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
    • 新增的注解支持:该版本新增了若干注解,用于简化Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml部署描述文件从该版本开始不再是必选的了。
    例子
    • 同步执行的方式
    package com.study.servletdemo.servletDemo;
    
    import com.study.servletdemo.dosomthing.DoSomThingRunning;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    @WebServlet("/test")
    public class Servlet4Sync1 extends HttpServlet {
    
    
        private static ThreadPoolExecutor executor =
                new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //TODO 这里面写逻辑
            System.out.println("当前处理请求的线程是:" + Thread.currentThread().getName());
            new DoSomThingRunning().run(); //调用一个方法.
            resp.getWriter().write("OK,get a response");
        }
    }
    
    • 异步返回的方式
    package com.study.servletdemo.servletDemo;
    
    import com.study.servletdemo.dosomthing.DoSomThingRunning;
    
    import javax.servlet.AsyncContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    @WebServlet(value = "/test1", asyncSupported = true)
    public class ServletSync2 extends HttpServlet {
    
        private static ThreadPoolExecutor executor =
                new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            AsyncContext asyncContext = req.startAsync(); //实现异步机制
            System.out.println("当前运行的Thread:" + Thread.currentThread().getName());
            executor.execute(() -> {
                new DoSomThingRunning().run();
                try {
                    asyncContext.getResponse().getWriter().write("ok,is response");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                asyncContext.complete();
            });
    // 非线程池的方式
    //         asyncContext.start(()->{
    //             new DoSomThingRunning().run();
    //             try {
    //                 asyncContext.getResponse().getWriter().write("ok,is response");
    //             } catch (IOException e) {
    //                 e.printStackTrace();
    //             }
    //             asyncContext.complete();
    //         });
        }
    }
    

    DeferredResult 异步处理

    在SpringMVC中,基于Servlet3.x的异步特性,Spring也提供了一套异步处理的方案。基于DeferredResult来实现异步处理。

    DeferredResult的主要流程:
    • 客户端〈即前端)请求之后,在Controller层返回一个DeferredResult对象,将其保存在内在队列或者可以访问它的列表中。

    • 其他请求进入之后,SpringMVC开始异步处理,DispatcherServlet与所有Filter的线程退出,response依然开放。

    • 当正常的结果完成返回之后,通过application进行返回DeferredResult中的sets值。

    • 注意:整体思路类似于callable中的返回值的方法。

    DeferredResult处理流程
    DeferredResult 的例子
    
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.async.DeferredResult;
    
    import java.util.Random;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    @RestController
    public class DeferredResultController {
    
        private LinkedBlockingQueue<DeferredResult> queue = new LinkedBlockingQueue<>();
    
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1000, 1500, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
    
        @RequestMapping("/get")
        public DeferredResult<String> getResult() {
            final DeferredResult<String> stringDeferredResult = new DeferredResult<>(3000L); //
    
            queue.add(stringDeferredResult);
    
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Random random = new Random();
                        int time = random.nextInt(5000);
                        System.out.println("休眠时间:" + time);
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //处理业务的代码
                    System.out.println("处理业务");
                    String rs = "返回值...";
                    stringDeferredResult.setResult(rs);
                }
            });
    
            stringDeferredResult.onTimeout(new Runnable() { //超时之后执行...
                @Override
                public void run() {
                    //TODO --自己的业务
                    System.out.println("超时处理...");
                    stringDeferredResult.setResult("超时的返回结果");
                }
            });
    
            stringDeferredResult.onCompletion(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务完成了..把队列中的deferredRueslt移除");
                    queue.remove();
                }
            });
    
            return stringDeferredResult;
    
        }
    
        @Scheduled(fixedRate = 1000)
        public void scheduleResult() {
            for (int i = 0; i < queue.size(); i++) {
                DeferredResult<String> stringDeferredResult = queue.poll();
                stringDeferredResult.setResult("result:" + i);
            }
        }
    
    }
    
    总结
    • 场景:
    • 使用DeferredResult可以明显提高系统吞吐量。
    • 在不使用异步处理的业务时,处理任务的线程一直都是主线程,也就是接收http请求的这根线程。一直会等到http请求返回或者异常之后,此线程才会释放。
    • 使用了DeferredResult之后,就会由另外的线程去处理,从而释放主线程。

    如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

    相关文章

      网友评论

        本文标题:Web容器中的异步处理

        本文链接:https://www.haomeiwen.com/subject/qhxpvrtx.html