Java Servlet线程中AsyncContext异步处理Http请求

目录
  • AsyncContext
  • AsyncContext使用示例及测试
    • 示例
    • 测试结果
  • AsyncContext应用场景
    • 背景
    • AsyncContext解决生产问题

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异步处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaWeb ServletConfig作用及原理分析讲解

    目录 基本概念 servlet 关系 servletconfig 作用 使用 获取 config 基础信息 携带信息 基本概念 servlet 关系 servlet 和 servletconfig 是一对一的关系: servletconfig 作用 它存储 web.xml 内的 servlet 标签内的所有信息: tomcat 在解析 web.xml 中的标签时,就会自动把 servlet 标签包装到 servletconfig 里面去: 使用 获取 config 基础信息 首先我们手动创建一个

  • Java Servlet实现表白墙的代码实例

    目录 一.表白墙简介 二.代码实现 1.约定前后端交互的接口 2.后端代码实现 3.前端代码实现 三.效果演示 总结 一.表白墙简介 在表白墙页面中包含三个文本框,分别表示表白者,表白对象,表白内容,在文本框中输入内容之后,内容能够保存,并且在下次启动页面的时候也能显示出之前所保存的内容. 二.代码实现 1.约定前后端交互的接口 要实现表白墙,首先就需要约定前后端的交互接口,在实际开发过程中,前端人员只负责前端开发,后端人员负责后端开发,那么就需要约定前端发送什么样的请求,后端处理请求之后再以什

  • JavaWeb Servlet生命周期细枝末节处深究

    目录 生命周期速览 优先级 servlet生命周期完整过程 servlet 所有核心方法解析 无参构造方法 init service destroy 适配器模式去除冗余接口 GenericServlet.java 生命周期速览 优先级 servlet 的声明周期由 tomcat 服务器自行管辖,程序员无法插手: 只要没有通过 url 访问 servlet,那他就永远不会先行实例化: 除非我们通过在 web.xml 的 servlet 标签下加上以下标签,即可立即实例化: </load-on-st

  • Java中Servlet的生命周期详解

    目录 Web基础和HTTP协议 什么是Servlet Servlet的生命周期 Web基础和HTTP协议 ┌─────────┐ ┌─────────┐ │░░░░░░░░░│ │O ░░░░░░░│ ├─────────┤ ├─────────┤ │░░░░░░░░░│ │ │ ├─────────┤ │ │ │░░░░░░░░░│ └─────────┘ └─────────┘ │ request 1 │ │─────────────────────>│ │ request 2 │ │───

  • JavaWeb通过IDEA配置Servlet操作流程详解

    目录 创建项目 引入外部 jar 安装 mysql-connector 编写 servlet 文件 编写 student.html 文件 配置 tomcat 服务器 运行并查看服务器 创建项目 首先创建一个空项目!!!注意是空项目!!! 点击 文件->新建->新模块 ,新建一个名称为 servlet02 的模块(注意该模块的生成位置应该在我们刚刚新建的空项目下面!!!) 之后右键点击新模块 servlet02,选择 “添加框架支持” 勾选 “web 应用程序” ,之后直接点击完成即可自动生成

  • ServletWebServerApplicationContext创建Web容器Tomcat示例

    目录 正文 创建Web服务 一.获取Web服务器工厂 1.1 选择导入Web工厂 二.getWebServer:获取Web服务 2.1 创建TomcatEmbeddedContext 2.2. 创建TomcatWebServer 2.2.1 启动Tomcat 初始化小结 startInternal:启动Internal NamingResources启动 Service启动 启动Engine 启动TomcatEmbeddedContext 启动Executor 启动MapperListener

  • 浅谈一下Servlet的定义以及运行原理

    目录 1.什么是servlet? 1.1 扩展web服务器端功能 1.2 servlet组件 2.如何写一个servlet? 3.servlet是如何运行的? 4.常见问题 4.1 状态码 4.2 404 4.3 500 4.4 405 1.什么是servlet? sun(oracle)公司制订的一种用来扩展web服务器端功能的组件规范. 背景: 常用的web服务器: apache http Server nginx:俄罗斯小伙子写的 IIS 以上服务器只能处理静态的资源请求.网页要提前写好,不

  • 基于Java子线程中的异常处理方法(通用)

    在普通的单线程程序中,捕获异常只需要通过try ... catch ... finally ...代码块就可以了.那么,在并发情况下,比如在父线程中启动了子线程,如何在父线程中捕获来自子线程的异常,从而进行相应的处理呢? 常见错误 也许有人会觉得,很简单嘛,直接在父线程启动子线程的地方try ... catch一把就可以了,其实这是不对的. 原因分析 让我们回忆一下Runnable接口的run方法的完整签名,因为没有标识throws语句,所以方法是不会抛出checked异常的.至于Runtime

  • Java线程中的关键字和方法示例详解

    目录 一.volatile关键字 1,volatile能保证内存可见性 2,编译器优化问题 二.wait和notify 1,wait()方法 2,notify()方法 3,notifyAll()方法 一.volatile关键字 1,volatile 能保证内存可见性 代码在写入 volatile 修饰的变量的时候 改变线程工作内存中volatile变量副本的值 将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时候 从主内存中读取volatile变量的最新值到线

  • Java子线程调用RequestContextHolder.getRequestAttributes()方法问题详解

    相信很多开发过程中都用过RequestContextHolder.getRequestAttributes(),没错,我也经常用,但今天出现了问题,获取到的实例是空的 原因是因为我新开了一个子线程,在子线程调用了RequestContextHolder.getRequestAttributes().实际获取到的是空的 然后查看了源码 ThreadLocal获取.一个请求到达容器后,Spring会把该请求Request实例通过setRequestAttributes方法 把Request实例放入该

  • Java中的异步与线程池解读

    目录 初始化线程的4种方式 1.继承Thread 2.实现Runnable 接口 3.实现Callable 接口+ FutureTask (可以拿到返回结果,可以处理异常) 4.线程池 创建线程池(ExecutorService) 1.Executors 工具类创建 2.原生方法创建线程池 3.线程池的运行流程 线程池创建 4. 四种常见的线程池 为什么要使用线程池 CompletableFuture 异步编排 1.创建异步对象 2.计算完成时(线程执行成功)回调方法 3.handle 方法(可

  • 详解Servlet 3.0/3.1 中的异步处理

    在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理.如果一个请求需要进行IO操作,比如访问数据库.调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题.即便是像Spring.Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的.

  • Java Servlet异步请求开启的简单步骤

    目录 1. 背景 2. Servlet同步请求 3. Servlet异步请求 4. 异步Servlet使用方法 5. Spring中的实现例子 附:异步对象监听器 总结 1. 背景 在研究长轮询的实现过程,有使用到Servlet3的异步请求.下面就来学习一下Servlet3的异步请求 现在Servlet的版本已经到了5 2. Servlet同步请求 以Tomcat服务器为例: Http请求到达Tomcat Tomcat从线程池中取出线程处理到达Tomcat的请求 将请求Http解析为HttpSe

  • springboot 正确的在异步线程中使用request的示例代码

    目录 起因: 发现有人踩过坑,但是没解决 尝试寻找官方支持 尝试自己解决 还是甩给官方 解决 结论 起因: 有后端同事反馈在异步线程中获取了request中的参数,然后下一个请求是get请求的话,发现会偶尔出现参数丢失的问题. 示例代码: @GetMapping("/getParams") public String getParams(String a, int b) { return "get success"; } @PostMapping("/po

  • Java 并发编程中如何创建线程

    简介 线程是基本的调度单位,它被包含在进程之中,是进程中的实际运作单位,它本身是不会独立存在.一个进程至少有一个线程,进程中的多个线程共享进程的资源. Java中创建线程的方式有多种如继承Thread类.实现Runnable接口.实现Callable接口以及使用线程池的方式,线程池将在后面文章中单独介绍,这里先介绍另外三种方式. 继承Thread类 优点:在run方法里可以用this获取到当前线程. 缺点:由于Java不支持多继承,所以如果继承了Thread类后就不能再继承其他类. public

  • 详解Java并发包中线程池ThreadPoolExecutor

    一.线程池简介 线程池的使用主要是解决两个问题:①当执行大量异步任务的时候线程池能够提供更好的性能,在不使用线程池时候,每当需要执行异步任务的时候直接new一个线程来运行的话,线程的创建和销毁都是需要开销的.而线程池中的线程是可复用的,不需要每次执行异步任务的时候重新创建和销毁线程:②线程池提供一种资源限制和管理的手段,比如可以限制线程的个数,动态的新增线程等等. 在下面的分析中,我们可以看到,线程池使用一个Integer的原子类型变量来记录线程池状态和线程池中的线程数量,通过线程池状态来控制任

  • 详解Java中CountDownLatch异步转同步工具类

    使用场景 由于公司业务需求,需要对接socket.MQTT等消息队列. 众所周知 socket 是双向通信,socket的回复是人为定义的,客户端推送消息给服务端,服务端的回复是两条线.无法像http请求有回复. 下发指令给硬件时,需要校验此次数据下发是否成功. 用户体验而言,点击按钮就要知道此次的下发成功或失败. 如上图模型, 第一种方案使用Tread.sleep 优点:占用资源小,放弃当前cpu资源 缺点: 回复速度快,休眠时间过长,仍然需要等待休眠结束才能返回,响应速度是固定的,无法及时响

随机推荐