偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

徹底搞懂Web異步編程模型

開發(fā) 前端
今天我們系統(tǒng)分析了在 Web 應用程序開發(fā)過程中,如何使用 Spring 框架提供的異步編程能力來提高系統(tǒng)的響應性。

長期以來,Spring Web MVC 運行在 Tomcat、JBoss 等 Servlet 容器上,是我們開發(fā) Web 服務的主流框架。但你要注意的是,Servlet 容器是阻塞式的,所以 WebMVC 也建立在阻塞 I/O 之上。

換句話說,任何一個請求的響應過程都是同步的,需要在服務器工作線程接收請求、阻塞等待 I/O 以及完成請求處理之后才能返回。

圖 1 同步請求處理過程示意圖圖 1 同步請求處理過程示意圖

這樣的同步請求處理機制對普通應用場景來說是合適的,但在一些特定場景下,這種同步機制會存在局限性,需要開發(fā)人員采用異步的方式來處理 Web 請求。這就引出了今天我們要討論的主題,Web 異步編程模型。

讓我們先從 Web 異步處理需求和場景開始說起。

Web 異步處理需求和場景

Web 異步處理的第一個應用場景是為了 提升系統(tǒng)性能

我們知道,同步請求處理機制采用的是一個請求對應一個線程的實現(xiàn)過程。這樣,系統(tǒng)請求數(shù)量越大,我們就需要創(chuàng)建越多的線程,而線程是一種資源,系統(tǒng)的響應能力會隨著資源的消耗而逐漸下降。但異步處理機制不需要在處理請求時全程保持某一個線程,這樣線程資源就能做到復用。

圖 2 異步請求處理過程示意圖圖 2 異步請求處理過程示意圖

接下來是異步處理的第二個應用場景,對于有些請求而言,我們實際上并不關注請求的返回結果,也就是說這些請求采用的是一種 即發(fā)即棄(Fire and Forget)模式。

這個模式有點類似于消息中間件的處理過程,請求線程發(fā)送請求然后直接返回。如果采用同步模式,那么請求必須等待服務端返回。因此,相比于異步處理,同步模式會造成浪費。

圖 3 即發(fā)即棄處理過程示意圖圖 3 即發(fā)即棄處理過程示意圖

最后,異步處理的第三種場景,在日常開發(fā)過程中, 某個請求需要處理大量業(yè)務數(shù)據(jù),這也是我們會經(jīng)常碰到的情況。比較典型的例子就是導出數(shù)據(jù)報表。在這種場景下,如果采用同步模式,很可能會導致出現(xiàn)請求超時。

這時候,合理的解決方案是先對請求做出快速響應,然后再啟動異步線程來執(zhí)行大數(shù)據(jù)處理邏輯。

圖 4 大數(shù)量請求處理過程示意圖圖 4 大數(shù)量請求處理過程示意圖

現(xiàn)在來簡單總結一下,從三個特定場景的異步模式應用中,我們可以看出:

對于傳統(tǒng)請求場景,異步模式能夠確保線程復用;

對于即發(fā)即棄場景,異步模式能夠節(jié)省系統(tǒng)資源;

而對于大數(shù)量請求場景,異步模式則能夠提高用戶體驗。

所以,如果能夠在復雜的業(yè)務場景中集成這三種場景中的異步調用機制,我們就可以高效處理 Web 請求。

那么,應該如何使用異步模式來高效應對這些場景呢?Spring 為我們提供了完整的解決方案,我們一起來看一下。

Spring Web 異步編程模型

異步處理的主要優(yōu)勢是調用方不必等待被調用方完成執(zhí)行過程,這就需要啟動新的線程。為了在一個新的線程中執(zhí)行目標方法,Spring 異步編程模型提供了一個全新的@Async 注解。該注解可以與 JDK 中的 Future 機制以及線程池進行無縫整合。我們先來看這個@Async 注解。

@Async 注解

想要在 Spring 應用程序中啟用異步編程模式,我們可以通過@EnableAsync 注解實現(xiàn)這一目標。常見的做法是在 Spring 配置類上添加這一注解。

@Configuration
@EnableAsync
public class SpringConfig { ... }

@Async 注解支持兩種處理模式,即 即發(fā)即棄模式和普通的請求響應模式。我們先來看即發(fā)即棄模式的代碼示例。

@Async
public void recordUserHealthData() {
 logger.info("Record user health data successfully.");
}

可以看到,我們在一個返回值為 void 的方法上添加了@Async 注解,這樣該方法中將以異步的方式進行執(zhí)行。

然后,我們來看一下請求響應式的異步方式代碼示例。

@Service
public class HealthService {
    @Async
    public Future<String> getHealthDescription() throws InterruptedException {
        LOGGER.info("Thread id: " + Thread.currentThread().getId());
        //睡眠 2 秒
        Thread.sleep(2000);
        String healthDescription = “health description”;
        LOGGER.info(processInfo);
        return new AsyncResult<String>(healthDescription);
    }
}

可以看到,這里我們在方法入口打印了當前的線程 ID,然后讓主線程睡眠 2 秒用來模擬長時間的業(yè)務處理流程。接著,我們返回異步調用的結果對象 AsyncResult。

AsyncResult 是 Spring 框架對 JDK 中 Future 接口的一種實現(xiàn),我們可以通過 AsyncResult 對象跟蹤異步調用的結果。為了更好理解上述方法的執(zhí)行過程,我們有必要先來看看 JDK 中的 Future 對象。

傳統(tǒng)模式調用和 Future 模式調用的對比可以參考圖 5。我們看到在 Future 模式調用過程中,客戶端在向服務器端發(fā)起請求之后馬上返回,可以繼續(xù)執(zhí)行其他任務直到服務器端通知 Future 調用的結果,體現(xiàn)了 Future 調用異步化特點。

圖 5 傳統(tǒng)調用(左)和 Future 機制(右)對比示意圖圖 5 傳統(tǒng)調用(左)和 Future 機制(右)對比示意圖

但原生的 Future 也有同步等待問題,因為通過 Future 對象直接獲取調用結果同樣會導致線程等待。為了解決這個問題,Java 8 中引入了 CompletableFuture 對原生的 Future 進行了優(yōu)化,可以直接通過 CompletableFuture 將異步執(zhí)行結果交給另外一個異步線程來處理。這樣在異步任務完成后,我們在獲取任務結果時則不需要等待。

例如,如果想要在異步執(zhí)行任務完成之后返回值,那么可以使用 CompletableFuture 的 supplyAsync() 方法,示例代碼如下所示。

@RequestMapping(value = "/health_description")
public CompletableFuture<String> syncHealthDescription () {
 CompletableFuture.supplyAsync(new Supplier<String>() {
           @Override
           public String get() {
               try {
                   return healthService.getHealthDescription().get();
               } catch (InterruptedException | ExecutionException e) {
                   LOGGER.error(e);
               }
               return"No health description found";
           }
        });
        return completableFuture;
}

WebAsyncTask

前面介紹的@Async 注解實際上是通用的,我們可以用它來完成包含 Web 請求在內的任意場景下的異步處理流程。而隨著 Spring Boot 的誕生,也出現(xiàn)了 WebAsyncTask 這一專門針對 Web 場景下的異步執(zhí)行組件。

相較@Async 注解,WebAsyncTask 為開發(fā)人員提供了更靈活的異步任務處理機制,并內置了異步回調、超時處理和異常處理。如果想要初始化一個 WebAsyncTask 對象,我們需要設置一個超時時間,并啟動一個線程對象。

public WebAsyncTask(long timeout, Callable<V> callable)

基于這一使用方式,我們先來看一下 WebAsyncTask 的簡單示例。

@RequestMapping(value = "task_normal", method = RequestMethod.GET)
public WebAsyncTask<String> task1() {
        System.out.println("The main Thread name is " +
Thread.currentThread().getName());
        // 此處模擬開啟一個異步任務
       WebAsyncTask<String> task1 = new WebAsyncTask<String>(4 * 1000L, () -> {
           System.out.println("The first Thread name is " +
Thread.currentThread().getName());
           Thread.sleep(2 * 1000L);
           return"task1 executed!";
        });
        // 任務執(zhí)行完成時調用該方法
        task1.onCompletion(() -> {
           System.out.println("task1 finished!");
        });
        // 可以繼續(xù)執(zhí)行其他操作
        System.out.println("task1 can do other things!");
        return task1;
}

可以看到,這里初始化了一個 WebAsyncTask 對象,并設置任務的超時時間為 4s。異步任務執(zhí)行采用 Thread.sleep 方法來進行模擬,這里設置異步線程的睡眠時間為 2s。然后,我們還通過 WebAsyncTask 的 onCompletion() 方法指定了任務執(zhí)行完成時的回調函數(shù)。

執(zhí)行以上代碼,我們在控制臺可以得到如下日志信息。

The main Thread name is http-nio-7000-exec-5
task1 can do other things!
The first Thread name is MvcAsync2
task1 finished!

顯然,我們先打印出了主線程的名稱,然后主線程可以繼續(xù)執(zhí)行并返回結果。然后我們啟動異步線程,并打印出該線程的名稱。當異步線程執(zhí)行完畢時,同樣打印出了這一信息。如果你在瀏覽器中訪問這個 HTTP 端點,那么可以獲取異步方法的正常返回值"task1 executed!"。

我們接著來看一下如何設置異常處理回調的方法,示例代碼如下所示。

@RequestMapping(value = "task_error", method = RequestMethod.GET)
public WebAsyncTask<String> getUserWithError() {
        System.out.println("The main Thread name is "
+ Thread.currentThread().getName());
        // 此處模擬開啟一個異步任務
       WebAsyncTask<String> task3 = new WebAsyncTask<String>(4 * 1000L, () -> {
           System.out.println("The second Thread name is "
+ Thread.currentThread().getName());
           int num = 1 / 0;
           System.err.println(num);
           return"";
        });
        // 發(fā)生異常時調用該方法
        task3.onError(() -> {
           System.err.println(Thread.currentThread().getName());
           System.err.println("task3 error occured!");
           return"";
        });
        // 任務執(zhí)行完成時調用該方法
        task3.onCompletion(() -> {
           System.out.println("task3 finished!");
        });
        // 可以繼續(xù)執(zhí)行其他操作
        System.out.println("task3 can do other things!");
        return task3;
}

這里設置了一個 onError() 回調,并通過除 0 操作觸發(fā)了這一回調,結果如下所示。

The main Thread name is http-nio-7000-exec-10
task3 can do other things!
The second Thread name is MvcAsync4
http-nio-7000-exec-1
task3 error occured!
task3 finished!

這樣,基于 WebAsyncTask 的異步編程模型就介紹完畢了。從上文中我們可以看出,WebAsyncTask 除了能夠實現(xiàn)異步調用,它所提供的異步編程模型充分考慮了異步執(zhí)行過程中可能出現(xiàn)的異常情況和超時機制。同時,基于回調的異步處理結果的獲取過程也顯得非常自然。相比@Async 注解,WebAsyncTask 的功能更加強大。

所以,在日常開發(fā)過程中,我建議你使用這個工具類來實現(xiàn)對 Web 請求的異步處理。

總結

今天我們系統(tǒng)分析了在 Web 應用程序開發(fā)過程中,如何使用 Spring 框架提供的異步編程能力來提高系統(tǒng)的響應性。

我們從異步處理場景講起,引出 Spring 中所提供了@Async 注解,該注解是對異步處理過程的抽象。在具體使用過程中,我們一般結合 CompletableFuture 來處理異步線程之間的交互過程。同時,針對 Web 開發(fā)場景,Spring 還專門提供了一個 WebAsyncTask 工具類來簡化開發(fā)過程。

在日常開發(fā)過程中,@Async 注解為開發(fā)人員提供的是一種通用型的異步編程,我們可以使用它在應用程序的各層組件中添加異步處理機制。而 WebAsyncTask 則專門面向 Web 請求處理,因此,如果你正在開發(fā) Web 應用程序,那么 WebAsyncTask 無疑是你的首選。

責任編輯:武曉燕 來源: 程序員技術充電站
相關推薦

2022-04-12 08:00:17

socket 編程網(wǎng)絡編程網(wǎng)絡 IO 模型

2025-05-06 01:14:00

系統(tǒng)編程響應式

2020-10-14 08:50:38

搞懂 Netty 線程

2013-04-01 15:38:54

異步編程異步編程模型

2025-04-21 04:00:00

2024-03-15 08:23:26

異步編程函數(shù)

2024-09-04 16:19:06

語言模型統(tǒng)計語言模型

2024-01-03 13:39:00

JS,Javascrip算法

2023-10-18 10:55:55

HashMap

2025-04-11 05:55:00

2025-01-13 16:00:00

服務網(wǎng)關分布式系統(tǒng)架構

2017-12-05 17:44:31

機器學習CNN卷積層

2025-06-30 00:32:43

策略模式算法MyBatis

2021-12-29 17:29:07

KubernetesEvents集群

2023-09-28 08:15:05

SpringBean加載

2021-10-11 11:58:41

Channel原理recvq

2023-05-29 08:12:38

2021-10-09 19:05:06

channelGo原理

2025-03-17 00:21:00

2009-11-09 10:43:51

WCF Web編程模型
點贊
收藏

51CTO技術棧公眾號