成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

大部分程序員不知道的 Servelt3 異步請求,原來這么簡單?

開發 后端
想象一下如果業務需要較長時間處理,那么這個 Tomcat 線程其實一直在被占用,隨著請求越來越多,可用 I/O 線程越來越少,直到被耗盡。這時后續請求只能等待空閑 Tomcat 線程,這將會加長了請求執行時間。

 用同步請求模型,所有動作都交給同一個 Tomcat 線程處理,所有動作處理完成,線程才會被釋放回線程池。

想象一下如果業務需要較長時間處理,那么這個 Tomcat 線程其實一直在被占用,隨著請求越來越多,可用 I/O 線程越來越少,直到被耗盡。這時后續請求只能等待空閑 Tomcat 線程,這將會加長了請求執行時間。

如果客戶端不關心返回業務結果,這時我們可以自定義線程池,將請求任務提交給線程池,然后立刻返回。

[[320914]]

也可以使用 Spring Async 任務,大家感興趣可以自行查找一下資料

但是很多場景下,客戶端需要處理返回結果,我們沒辦法使用上面的方案。在 Servlet2 時代,我們沒辦法優化上面的方案。

不過等到 Servlet3 ,引入異步 Servlet 新特性,可以完美解決上面的需求。

異步 Servlet 執行請求流程:

將請求信息解析為 HttpServletRequest

分發到具體 Servlet 處理,將業務提交給自定義業務線程池,請求立刻返回,Tomcat 線程立刻被釋放

當業務線程將任務執行結束,將會將結果轉交給 Tomcat 線程

通過 HttpServletResponse 將響應結果返回給等待客戶端

引入異步 Servlet3 整體流程如下:

使用異步 Servelt,Tomcat 線程僅僅處理請求解析動作,所有耗時較長的業務操作全部交給業務線程池,所以相比同步請求, Tomcat 線程可以處理 更多請求。

雖然我們將業務處理交給業務線程池異步處理,但是對于客戶端來講,其還在同步等待響應結果。

可能有些同學會覺得異步請求將會獲得更快響應時間,其實不是的,相反可能由于引入了更多線程,增加線程上下文切換時間。

雖然沒有降低響應時間,但是通過請求異步化帶來其他明顯優點:

  • 可以處理更高并發連接數,提高系統整體吞吐量
  • 請求解析與業務處理完全分離,職責單一
  • 自定義業務線程池,我們可以更容易對其監控,降級等處理
  • 可以根據不同業務,自定義不同線程池,相互隔離,不用互相影響

所以具體使用過程,我們還需要進行的相應的壓測,觀察響應時間以及吞吐量等其他指標,綜合選擇。

異步 Servelt 使用方式

異步 Servelt 使用方式不是很難,阿粉總結就是下面三板斧:

  1. HttpServletRequest#startAsync 獲取 AsyncContext 異步上下文對象
  2. 使用自定義的業務線程池處理業務邏輯
  3. 業務線程處理結束,通過 AsyncContext#complete 返回響應結果

下面的例子將會使用 SpringBoot ,Web 容器選擇 Tomcat

示例代碼如下:

  1. ExecutorService executorService = Executors.newFixedThreadPool(10); 
  2.  
  3. @RequestMapping("/hello"
  4. public void hello(HttpServletRequest request) { 
  5.     AsyncContext asyncContext = request.startAsync(); 
  6.     // 超時時間 
  7.     asyncContext.setTimeout(10000); 
  8.     executorService.submit(() -> { 
  9.         try { 
  10.             // 休眠 5s,模擬業務操作 
  11.             TimeUnit.SECONDS.sleep(5); 
  12.             // 輸出響應結果 
  13.             asyncContext.getResponse().getWriter().println("hello world"); 
  14.             log.info("異步線程處理結束"); 
  15.         } catch (Exception e) { 
  16.             e.printStackTrace(); 
  17.         } finally { 
  18.             asyncContext.complete(); 
  19.         } 
  20.     }); 
  21.     log.info("servlet 線程處理結束"); 

瀏覽器訪問該請求將會同步等待 5s 得到輸出響應,應用日志輸出結果如下:

  1. 2020-03-24 07:27:08.997  INFO 79257 --- [nio-8087-exec-4] com.xxxx   : servlet 線程處理結束 
  2. 2020-03-24 07:27:13.998  INFO 79257 --- [pool-1-thread-3] com.xxxx   : 異步線程處理結束 

這里我們需要注意設置合理的超時時間,防止客戶端長時間等待。

SpringMVC

Servlet3 API ,無法使用 SpringMVC 為我們提供的特性,我們需要自己處理響應信息,處理方式相對繁瑣。

SpringMVC 3.2 基于 Servelt3 引入異步請求處理方式,我們可以跟使用同步請求一樣,方便使用異步請求。

SpringMVC 提供有兩種異步方式,只要將 Controller 方法返回值修改下述類即可:

  • DeferredResult
  • Callable

DeferredResult

DeferredResult 是 SpringMVC 3.2 之后引入新的類,只要讓請求方法返回DeferredResult,就可以快速使用異步請求,示例代碼如下:

  1. ExecutorService executorService = Executors.newFixedThreadPool(10); 
  2.  
  3. @RequestMapping("/hello_v1"
  4. public DeferredResult<String> hello_v1() { 
  5.     // 設置超時時間 
  6.     DeferredResult<String> deferredResult = new DeferredResult<>(7000L); 
  7.     // 異步線程處理結束,將會執行該回調方法 
  8.     deferredResult.onCompletion(() -> { 
  9.         log.info("異步線程處理結束"); 
  10.     }); 
  11.     // 如果異步線程執行時間超過設置超時時間,將會執行該回調方法 
  12.     deferredResult.onTimeout(() -> { 
  13.         log.info("異步線程超時"); 
  14.         // 設置返回結果 
  15.         deferredResult.setErrorResult("timeout error"); 
  16.     }); 
  17.     deferredResult.onError(throwable -> { 
  18.         log.error("異常", throwable); 
  19.         // 設置返回結果 
  20.         deferredResult.setErrorResult("other error"); 
  21.     }); 
  22.     executorService.submit(() -> { 
  23.         try { 
  24.             TimeUnit.SECONDS.sleep(5); 
  25.             deferredResult.setResult("hello_v1"); 
  26.             // 設置返回結果 
  27.         } catch (Exception e) { 
  28.             e.printStackTrace(); 
  29.             // 若異步方法內部異常 
  30.             deferredResult.setErrorResult("error"); 
  31.         } 
  32.     }); 
  33.     log.info("servlet 線程處理結束"); 
  34.     return deferredResult; 
  35.  

創建 DeferredResult 實例時可以傳入特定超時時間。另外我們可以設置默認超時時間:

  1. # 異步請求超時時間 
  2. spring.mvc.async.request-timeout=2000 

如果異步程序執行完成,可以調用 DeferredResult#setResult返回響應結果。此時若有設置 DeferredResult#onCompletion 回調方法,將會觸發該回調方法。

同時我們還可以設置超時回調方法 DeferredResult#onTimeout,一旦異步線程執行超時,將會觸發該回調方法。

最后 DeferredResult 還提供其他異常的回調方法 onError,起初阿粉以為只要異步線程內發生異常,就會觸發該回調方法。嘗試在異步線程內拋出異常,但是無法成功觸發。

后續阿粉查看這個方法的 doc,當 web 容器線程處理異步請求時發生異常,才能成功觸發。

 

Callable

Spring 另外還提供一種異步請求使用方式,直接使用 JDK Callable。示例代碼如下:

  1. @RequestMapping("/hello_v2"
  2. public Callable<String> hello_v2() { 
  3.     return new Callable<String>() { 
  4.         @Override 
  5.         public String call() throws Exception { 
  6.             TimeUnit.SECONDS.sleep(5); 
  7.             log.info("異步方法結束"); 
  8.             return "hello_v2"
  9.         } 
  10.     }; 

默認情況下,直接執行將會輸出 WARN 日志

這是因為默認情況使用 SimpleAsyncTaskExecutor 執行異步請求,每次調用執行都將會新建線程。由于這種方式不復用線程,生產不推薦使用這種方式,所以我們需要使用線程池代替。

我們可以使用如下方式自定義線程池:

  1. @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) 
  2. public AsyncTaskExecutor executor() { 
  3.     ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); 
  4.     threadPoolTaskExecutor.setThreadNamePrefix("test-"); 
  5.     threadPoolTaskExecutor.setCorePoolSize(10); 
  6.     threadPoolTaskExecutor.setMaxPoolSize(20); 
  7.     return threadPoolTaskExecutor; 

注意 Bean 名稱一定要是 applicationTaskExecutor,若不一致, Spring 將不會使用自定義線程池。

或者可以直接使用 SpringBoot 配置文件方式配置代替:

  1. # 核心線程數 
  2. spring.task.execution.pool.core-size=10 
  3. # 最大線程數 
  4. spring.task.execution.pool.max-size=20 
  5. # 線程名前綴 
  6. spring.task.execution.thread-name-prefix=test 
  7. # 還有另外一些配置,讀者們可以自行配置 

這種方式異步請求的超時時間只能通過配置文件方式配置。

  1. spring.mvc.async.request-timeout=10000 

如果需要為單獨請求的配置特定的超時時間,我們需要使用 WebAsyncTask 包裝 Callable 。

  1. @RequestMapping("/hello_v3"
  2. public WebAsyncTask<String> hello_v3() { 
  3.     System.out.println("asdas"); 
  4.     Callable<String> callable=new Callable<String>() { 
  5.         @Override 
  6.         public String call() throws Exception { 
  7.             TimeUnit.SECONDS.sleep(5); 
  8.             log.info("異步方法結束"); 
  9.             return "hello_v3"
  10.         } 
  11.     }; 
  12.     // 單位 ms 
  13.     WebAsyncTask<String> webAsyncTask=new WebAsyncTask<>(10000,callable); 
  14.     return webAsyncTask; 

總結

SpringMVC 兩種異步請求方式,本質上就是幫我們包裝 Servlet3 API ,讓我們不用關心具體實現細節。雖然日常使用我們一般會選擇使用 SpringMVC 兩種異步請求方式,但是我們還是需要了解異步請求實際原理。所以大家如果在使用之前,可以先嘗試使用 Servlet3 API 練習,后續再使用 SpringMVC。

  • Referencehttps://www.baeldung.com/spring-deferred-result
  • https://spring.io/blog/2012/05/07/spring-mvc-3-2-preview-introducing-servlet-3-async-support

 

責任編輯:武曉燕 來源: Java極客技術
相關推薦

2025-03-11 09:04:26

2020-04-15 16:07:01

程序員技術數據

2012-11-30 10:07:49

大數據云儲存數據挖掘

2021-02-08 22:32:43

程序員 靜態網頁

2019-09-12 09:56:13

程序員技能開發者

2019-10-11 10:05:30

程序員固態硬盤Google

2018-05-08 15:30:46

程序員代碼框架

2018-07-10 11:33:58

計算器iPhone刪除

2019-11-24 19:34:04

HTTP長連接短連接

2019-06-12 10:35:49

程序員高效工具開源

2022-08-08 11:13:35

API接口前端

2011-08-23 13:50:17

程序員

2020-03-03 18:59:47

CDN緩存程序員

2018-09-20 17:05:01

前端程序員JavaScript

2019-07-12 15:28:41

緩存數據庫瀏覽器

2023-10-11 08:16:42

客戶端服務器內容

2021-11-30 22:59:28

程序員IT架構師

2013-11-21 13:35:19

程序員牛人

2021-03-01 19:13:45

YAML程序員數據

2022-10-29 17:34:18

JVMJava
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美日本韩国一区二区 | 中国一级特黄真人毛片 | 日韩和的一区二区 | 日韩在线一区二区三区 | 天天视频一区二区三区 | 亚洲精品视频在线 | 亚洲精品视频在线 | 一区二区三区视频在线观看 | 国产一区二区在线视频 | 综合网中文字幕 | 国产精品免费看 | 激情五月婷婷综合 | 久久91av| 免费人成激情视频在线观看冫 | 日韩在线看片 | 国产欧美一区二区三区日本久久久 | 999久久久久久久久6666 | 久久久久久国产精品免费免费狐狸 | 国产做a爱免费视频 | 国产精品91视频 | 男女啪啪网址 | 国产成人av电影 | 9久9久9久女女女九九九一九 | 婷婷久久综合 | 情侣酒店偷拍一区二区在线播放 | 免费观看一级毛片视频 | 国产日韩一区二区三区 | 97视频人人澡人人爽 | 精品日韩一区二区 | 国产三级 | 日韩一区二区视频 | av香港经典三级级 在线 | 精品一区二区在线观看 | 国产精品视频网 | 日韩一区二区福利视频 | 亚洲视频一| 欧美一级久久 | 日韩欧美在 | 久久久久久久久久久久久9999 | 久久久久久毛片免费观看 | 在线2区|