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
繁體地址,請注明出處:http://hk.pswp.cn/diannao/87123.shtml
英文地址,請注明出處:http://en.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