文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

JavaServlet线程中AsyncContext异步处理Http请求

2023-03-01 11:37

关注

AsyncContext

AsyncContextServlet 3.0使Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该Servlet线程。在接收到请求之后,Servlet线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度

Servlet 3.0新增了异步处理,可以先释放容器分配给请求的线程与相关资源,减轻系统负担,原先释放了容器所分配线程的请求,其响应将被延后,可以在处理完成(例如长时间运算完成、所需资源已获得)时再对客户端进行响应。

在Servlet 3.0中,在ServletRequest上提供了startAsync()方法,该方法会根据请求的ServletRequestServletResponse创建AsyncContext对象。

    @Override
    public AsyncContext startAsync() {
        return startAsync(getRequest(),response.getResponse());
    }
    @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;
    }

请求调用startAsyncServlet线程将会被释放,请求交由其他线程去处理,如果业务线程没有处理完,客户端将不会收到响应,直到调用AsyncContextcomplete()dispatch(ServletContext context, String path)方法为止,dispatch方法会根据path进行重定向。AsyncContextImpl大致代码如下:

    @Override
    public void complete() {
        if (log.isDebugEnabled()) {
            logDebug("complete   ");
        }
        check();
        request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
    }
    @Override
    public void dispatch() {
        check();
        String path;
        String cpath;
        ServletRequest servletRequest = getRequest();
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest sr = (HttpServletRequest) servletRequest;
            path = sr.getRequestURI();
            cpath = sr.getContextPath();
        } else {
            path = request.getRequestURI();
            cpath = request.getContextPath();
        }
        if (cpath.length() > 1) {
            path = path.substring(cpath.length());
        }
        if (!context.getDispatchersUseEncodedPaths()) {
            path = UDecoder.URLDecode(path, StandardCharsets.UTF_8);
        }
        dispatch(path);
    }

AsyncContext使用示例及测试

示例

设置Tomcat线程数为1

server:
  port: 9099
  servlet:
    context-path: /server/v1
  # 设置Tomcat线程数为1
  tomcat:
    min-spare-threads: 1
    max-threads: 1

Controller

@RestController
public class AsyncTestController {
    private final ScheduledExecutorService timeoutChecker = new ScheduledThreadPoolExecutor(1, threadFactory);
    private static boolean result = false;
    @PostMapping("/async")
    public void async(@RequestBody Request re1, HttpServletRequest request, HttpServletResponse response) {
        // 创建AsyncContext
        AsyncContext asyncContext = request.startAsync(request, response);
        String name = re1.getUsername();
        // 设置处理超时时间2s
        asyncContext.setTimeout(2000L);
        // asyncContext监听
        asyncContext.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
            }
            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                asyncContext.getResponse().getWriter().print(name + ":timeout");
                asyncContext.complete();
            }
            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
            }
            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
            }
        });
        // 定时处理业务,处理成功后asyncContext.complete();完成异步请求
        timeoutChecker.scheduleWithFixedDelay(() -> {
            try {
                if (result) {
                    asyncContext.getResponse().getWriter().print(name);
                    asyncContext.complete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }, 0, 100L, TimeUnit.MILLISECONDS);
    }
    // 模拟业务处理完成
    @PostMapping("/notify")
    public void notify(Boolean s) {
        result = s;
    }
}

测试结果

并发5,两个循环

10条并发请求都能够处理,并且处理的时间都是2s左右(因为设置的超时时间是2s),通过该测试结果可以看出,使用AsyncContext可以在容器资源有限的情况下处理更多的请求,这在高并发场景下就比较有用了。

AsyncContext应用场景

使用AsyncContext实现的Http长轮询在许多的中间件的信息同步场景中应用广泛,例如Nacos配置中心和Apache Shenyu网关。

背景

公司一个系统是Netty实现的TCP协议的服务,其中的一个业务是设备请求后台接口查询支付结果,接口的处理逻辑是收到请求后就将请求放到一个队列中,然后由业务线程异步处理,当收到支付结果完成后将响应给客户端支付结果,该接口的超时时间是2s,如果2s查不到支付结果就返回给客户端查不到结果,客户端收到该错误后重新发起查询,直到客户端的整个业务超时。

公司由于服务架构调整,要将该系统改造成基于SpringBoot的Http协议接口,如果”支付结果查询接口“不做机制的变更,就会导致每一次结果查询都会阻塞等待队列中查询支付结果的查询,因为支付是异步的,所以支付结果查询会比较耗时,如果机制不改那么如果并发增大的话会导致服务器的处理请求线程全部被打满,整个服务对于其他请求,其他业务都变得不可用了,这个结果是不可以接受的。

AsyncContext解决生产问题

基于示例中的demo进行业务改造

开启异步,设置整个异步接口处理的超时时间(2s),设置Listener主要用于处理接口超时,阻塞队列处理查询支付结果,查到结果后调用complete完成该长轮询,如果2s没有查到结果,那就返回查询超时,客户端继续轮询。

到此这篇关于Java Servlet线程中AsyncContext异步处理Http请求的文章就介绍到这了,更多相关Java AsyncContext异步处理内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯