SpringMVC异步处理Servlet

使用SpringMVC异步处理Servlet解决的问题

  1. 可以不阻塞有限的tomcat 线程(默认是200~250个,springboot3是200个),确保网络请求可以持续响应
  2. 特定业务使用自定义线程池,可以处理的业务量更大
  3. 对上层业务完全无感知(但如果中间链路有超时,则需要注意,比如 nginx 代理60秒超时)
  4. SpringMVC 的包装比较好,可以完全无感 Servlet 的底层实现

注意事项

  1. springboot3里面会默认开启异步,虽然官方文档说,如果使用了AbstractAnnotationConfigDispatcherServletInitializer则会开启异步支持,但我看代码并没有使用,实际也是开启的(可能还有我不懂的,比如使用了@EnableAsync注解,先保留意见)。SpringMVC 会默认将所有的 Servlet 和 Filter 都注册为支持异步,但是否需要开启异步是根据返回值的类型判断的。
  2. 官方文档说返回结果支持DeferredResult和Callable,实测其实还有其他的类型可以支持,比如CompletableFuture。
  3. 对于 Callable 的返回结果,处理流程如下:
    1. 对于这个类型的返回值,Spring 会采用CallableMethodReturnValueHandler进行处理异步结果,注意这里的结果类型都是Callable。
    2. CallableMethodReturnValueHandler会调用 org.springframework.web.context.request.async.WebAsyncManager#startCallableProcessing(org.springframework.web.context.request.async.WebAsyncTask<?>, java.lang.Object…)处理结果,Callable 是包装成WebAsyncTask执行的。
      1. 其中的拦截器之类的都忽略,但这有非常重要的一点this.taskExecutor.submit(() -> {});,这里是 SpringMVC 会将异步的任务提交到线程池处理,也就是调用Callable.call()来获取结果,也就意味着这个线程池是阻塞的。而 Servlet 线程会在此提交到线程池后释放(假定此处的 servlet 线程是http-nio-7001-exec-1)。
      2. 所谓释放线程,其实就是 servlet 执行结束了,然后 Filter 也会执行结束,相当于整个请求的线程执行全部结束,仅仅是,没有给 response 结果而已。
      3. 这一步的线程池taskExecutor,默认是每次new Thread 的,存在风险,需要替换为执行线程池,可以实现 MVC 配置,并指定线程池org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configureAsyncSupport。
    3. 在异步执行完成后,SpringMVC 会再次触发DispatcherServlet中doDispatch(这也是个坑),在这里直接将结果返回,不再执行 Controller。这是因为 Request 中已经标记request.getDispatcherType()是ASYNC值。
      1. HandlerInterceptor拦截器的实现类需要注意,因为DispatcherServlet中doDispatch会被再次调用,所以preHandle方法在一开始会调用一次,异步执行完成后,发送结果给客户端的时候会重复调用一次,如果不希望执行,可以用DispatcherType.ASYNC.equals(request.getDispatcherType())判断,并跳过执行,注意返回结果是 true即可。
      2. 再次触发的doDispatch,通常是一个新的线程(如:http-nio-7001-exec-2),因为线程不同,所以在 ThreadLocal 中存储的全链路跟踪 TraceId 会丢失,需要采用其他方式,比如放到 Request的Attribute 里面。
  4. WebAsyncTask 和 Callable 一样,也存在线程池问题。
  5. 对于DeferredResult的返回结果,处理流程如下:
    1. DeferredResultMethodReturnValueHandler
    2. org.springframework.web.context.request.async.WebAsyncManager#startDeferredResultProcessing
      1. 这里没有线程池,因为DeferredResult 需要你在业务执行的地方setResult,Spring 会在setResult后触发后续链路。
  6. CompletableFuture、Mono、Flux 返回值类型:会被认为是 Reactive 类型的返回值,通过ResponseBodyEmitterReturnValueHandler处理,最终会包装为DeferredResultSubscriber,执行到org.springframework.web.context.request.async.WebAsyncManager#startDeferredResultProcessing中,和DeferredResult的处理方式一样。
    1. 注意,在handleReturnValue 中会有判断,如果异步的返回 Reactive 对象是同步完成态,比如 Mono 只有一个确定的结果,里面没有采用订阅的模式进行异步处理,或者 Future 是完结态,那么handleReturnValue 会直接同步返回结果,而不是异步处理。所以开发的时候要注意:
      1. 一定要自己启动异步线程,在Future 中做业务逻辑,比如直接用CompletableFuture.supplyAsync(()->{}, threadPool)
      2. Publisher.subscribe订阅的时候,启动异步线程threadPool.submit()->{ subscriber.onSubscribe(subscription); --> Subscription.request(x) --> subscriber.onNext(T); },在异步线程中onNext时输出结果,才能确保在 Servlet 层面是异步的,非阻塞的。

结论

只有 Callable 用了简单线程池,存在线程问题,如果使用需要指定线程池,但也只有Callable使用最简单,return 即可。

另外,推荐采用DeferredResult和CompletableFuture类型的返回值,需要自己在线程池中处理业务并赋值结果。

代码示例

package com.test;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import reactor.core.publisher.Mono;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;@Slf4j
@RestController
@RequestMapping("/restapi/testAsync")
public class TestAsyncToolboxController {private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,100,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10000),new ThreadPoolExecutor.AbortPolicy());@RequestMapping("/test")public DeferredResult<Object> test(HttpServletRequest request, HttpServletResponse response) {log.info("TestAsyncToolboxController.test 1 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());DeferredResult<Object> deferredResult = new DeferredResult<>();threadPoolExecutor.submit(() -> {try {log.info("TestAsyncToolboxController.test 2 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());TimeUnit.SECONDS.sleep(5);log.info("TestAsyncToolboxController.test 3 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());} catch (InterruptedException e) {deferredResult.setResult("exception");throw new RuntimeException(e);}log.info("TestAsyncToolboxController.test 4 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());deferredResult.setResult("success");});return deferredResult;}@RequestMapping("/test3")public CompletableFuture<String> test3(HttpServletRequest request, HttpServletResponse response) {log.info("TestAsyncToolboxController.test3 1 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {try {log.info("TestAsyncToolboxController.test3 2 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());TimeUnit.SECONDS.sleep(5);log.info("TestAsyncToolboxController.test3 3 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());} catch (InterruptedException e) {throw new RuntimeException(e);}log.info("TestAsyncToolboxController.test3 4 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());return "success";}, threadPoolExecutor);return stringCompletableFuture;}@RequestMapping("/test4")public Mono<String> test4(HttpServletRequest request, HttpServletResponse response) {log.info("TestAsyncToolboxController.test4 1 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());try {return Mono.from(new Publisher<String>() {@Overridepublic void subscribe(Subscriber<? super String> subscriber) {threadPoolExecutor.submit(() -> {try {log.info("TestAsyncToolboxController.test4 2 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());TimeUnit.SECONDS.sleep(5);log.info("TestAsyncToolboxController.test4 3 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());} catch (InterruptedException e) {throw new RuntimeException(e);}log.info("TestAsyncToolboxController.test4 4 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());Subscription subscription = new Subscription() {@Overridepublic void request(long n) {log.info("TestAsyncToolboxController.test4 7 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());subscriber.onNext("success");}@Overridepublic void cancel() {log.info("TestAsyncToolboxController.test4 8 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());}};log.info("TestAsyncToolboxController.test4 10 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());subscriber.onSubscribe(subscription);log.info("TestAsyncToolboxController.test4 9 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());});log.info("TestAsyncToolboxController.test4 6 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());}});} finally {log.info("TestAsyncToolboxController.test4 5 isAsyncStarted={}, isAsyncSupported={}", request.isAsyncStarted(), request.isAsyncSupported());}}
}

其他

Servlet 的异步性能和 Reactor 的性能是否存在较大差异?为什么 Servlet 依然健在,且没有明显的被 Reactor 模式替换的迹象?

参考

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-async.html
https://blog.csdn.net/sinat_33543436/article/details/88971367

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/87123.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

同步与异步编程范式全景研究——从CPU时钟周期到云原生架构的范式演进

第一章 时空观的根本分歧 1.1 物理时间的约束性 同步操作的本质是对牛顿绝对时间的服从&#xff0c;其阻塞特性源于冯诺依曼体系下指令顺序执行的基因。现代CPU的流水线技术&#xff08;如Intel Hyper-Threading&#xff09;通过指令级并行实现伪异步&#xff0c;但开发者仍需…

【零散技术】5分钟完成Odoo18 登陆页面全自定义

序言:时间是我们最宝贵的财富,珍惜手上的每个时分 从最初的tinyERP到Open ERP&#xff0c;再由OpenERP到Odoo&#xff0c;虽然UI已经过了多次大改&#xff0c;Odoo登录界面依旧丑陋&#xff0c;同时还有各种Odoo版权信息&#xff0c;对于定制项目而言是不友好的。 今天以Odoo18…

Vue3 + TypeScript + Element Plus + el-pagination 分页查询实例分享

前端技术栈&#xff1a;Vue3 TypeScript Element Plus el-pagination 后端技术栈&#xff1a;Java Spring Boot Mybatis 应用异常情况说明&#xff1a;点击页码2&#xff0c;会发送两次请求&#xff0c;并且自动跳回页码1 代码&#xff1a; Reagent.vue <script set…

LoadRunner 2023 安装部署

下载地址&#xff1a;链接: https://caiyun.139.com/w/i/2nQQRYCZ1Ssjl 提取码:3gz0 复制内容打开139-云盘 主要下载Micro_Focus_LoadRunner_2023_Community_Edition.exe来安装就可以。 如要汉化&#xff0c;则再下载安装Language_Packs.exe的安装包 说明&#xff1a;LoadR…

ABC410 : F - Balanced Rectangles

https://atcoder.jp/contests/abc410/tasks/abc410_fhttps://atcoder.jp/contests/abc410/tasks/abc410_f首先可以一眼看出暴力 &#xff1a;枚举左上角和右下角&#xff0c;用前缀和算出矩形中#的数量&#xff0c;判断即可 但这样是,爆!!! 考虑优化&#xff0c;我们可以枚举…

嵌入式学习笔记 - HAL库对外设的封装

一 外设封装结构 HAL库对外设的封装使用了xx_HandleTypeDef类型的外设句柄结构体&#xff0c;这个句柄结构体的第一个成员Instance(xx_TypeDef类型)一般为该外设的所有寄存器的起始基地址&#xff0c;第二个成员Init&#xff08;xx_InitTypeDef类型&#xff09;一般为该外设的设…

高精度模板

加法 P1601 AB Problem&#xff08;高精&#xff09; #include<iostream>using namespace std; const int N 1e6 10; int a[N],b[N],c[N]; int len1,len2,lenMax; //长度要提前定义在全局&#xff0c;在函数中要使用 void add(int c[],int a[],int b[]) {for(int i0…

monorepo使用指北

|  WARN  node_modules is present. Lockfile only installation will make it out-of-date  ERR_PNPM_FETCH_404  GET https://registry.npmjs.org/common%2Fcommon: Not Found - 404 This error happened while installing a direct dependency of G:\monorepo\vue3 comm…

Java八股文——Spring「MyBatis篇」

与传统的JDBC相比&#xff0c;MyBatis的优点&#xff1f; 面试官您好&#xff0c;MyBatis相比于传统的JDBC&#xff0c;它并不是要完全颠覆JDBC&#xff0c;而是作为JDBC的一个强大的“增强框架”。它的核心价值在于&#xff0c;在保留了SQL最大灵活性的前提下&#xff0c;极大…

JavaScript基础-常用的鼠标事件

一、前言 在前端开发中&#xff0c;鼠标事件 是实现用户交互的重要手段之一。通过监听用户的点击、移动、悬停等操作&#xff0c;我们可以构建出丰富而灵活的网页交互体验。 本文将带你深入了解&#xff1a; JavaScript 中常见的鼠标事件&#xff1b;各类鼠标事件的触发时机…

windows录频软件

一.很反感有些做软件的&#xff0c;把别人开源的改个界面收费&#xff0c;所以我找了一个开源免费的。 二.准备工具 一台电脑&#xff0c; Captura:完全开源免费的录频软件。 ffmpeg&#xff1a;音频格式转换软件&#xff0c;这可是非常大名鼎鼎的工具。 三.安装Captura 网址…

python中的模块化编程:日期模块、math算术模块、random模块

内置模块&#xff08;math、random、时间&#xff09;自定义模块&#xff08;自己写的部分代码&#xff09;第三方模块&#xff08;引入的第三方代码库的模块&#xff09; math模块 import math#圆周率 print(math.pi) #自然常数 print(math.e) #圆周率的二倍 print(math.tau…

【学习笔记】Langchain基础(二)

前文&#xff1a;【学习笔记】Langchain基础 文章目录 8 [LangGraph] 实现 Building Effective Agents&#xff0c;各种 workflows 及 AgentAugmented LLMPrompt ChainingParallelizationRoutingOrchestrator-Worker (协调器-工作器)Evaluator-optimizer (Actor-Critic)Agent 8…

Java大模型开发入门 (9/15):连接外部世界(中) - 向量嵌入与向量数据库

前言 在上一篇文章中&#xff0c;我们成功地将一篇长文档加载并分割成了一系列小的文本片段&#xff08;TextSegment&#xff09;。我们现在有了一堆“知识碎片”&#xff0c;但面临一个新问题&#xff1a;计算机如何理解这些碎片的内容&#xff0c;并找出与用户问题最相关的片…

Windows下MySQL安装全流程图文教程及客户端使用指南(付整合安装包)

本教程是基于5.7版本安装&#xff0c;5.7和8.0的安装过程大差不差 安装包「windows上mysql中安装包资源」 链接&#xff1a;https://pan.quark.cn/s/de275899936d 一、安装前的准备 1.1 获取 MySQL 安装程序 官网 前往 MySQL 官方下载页面&#xff0c;下载适用于 Windows 系…

笔记 软件工程复习

第一章 软件工程学概述 1.1 软件危机&#xff08;Software Crisis&#xff09; 概念 定义&#xff1a;软件危机指在计算机软件开发与维护过程中遇到的一系列严重问题&#xff0c;源于1960年代软件复杂度激增与传统开发方法失效的矛盾。 本质&#xff1a;软件规模扩大 → 开…

GaussDB创建数据库存储

示例一&#xff1a; 下面是一个简单的GaussDB存储过程示例&#xff1a; –创建一个存储过程。 CREATE OR REPLACE PROCEDURE prc_add (param1 IN INTEGER,param2 IN OUT INTEGER ) AS BEGINparam2: param1 param2;dbe_output.print_line(result is: ||to_char(param…

基于51单片机的校园打铃及灯控制系统

目录 具体实现功能 设计介绍 资料内容 全部内容 资料获取 具体实现功能 具体功能&#xff1a; &#xff08;1&#xff09;实时显示当前时间&#xff08;年月日时分秒星期&#xff09;&#xff0c;LED模式指示灯亮。 &#xff08;2&#xff09;按下“打铃”和“打铃-”按键…

PHP+mysql雪里开轻量级报修系统 V1.0Beta

# PHP雪里开轻量级报修系统 V1.0Beta ## 简介 这是一个基于PHP7和MySQL5.6的简易报修系统&#xff0c;适用于学校、企业等机构的设备报修管理。 系统支持学生提交报修、后勤处理报修以及系统管理员管理用户和报修记录。 初代版本V1.0&#xff0c;尚未实际业务验证&#xff0c;…

XCTF-misc-base64÷4

拿到一串字符串 666C61677B45333342374644384133423834314341393639394544444241323442363041417D转换为字符串得到flag