高併發場景下的Web異步任務WebAsyncTask

同步與異步

Web同步調用

瀏覽器/客戶端發起一個請求,Web服務器開啓一個線程來處理請求,當請求處理完成之後,Web服務器返回處理結果,這就是同步調用。html

WebAsyncTask-同步調用

本文及圖片參考(下同) - 木子旭:異步調用【WebAsyncTask】java

在普通的場景下,若是服務器負載不大,而且後端服務也給力,同步調用並無什麼問題。git

但在高併發場景下,請求服務端的線程總數是有限的,若是某個線程一直處於阻塞狀態,就會影響系統的吞吐量。github

WebAsyncTask-同步調用

Web異步調用

所謂異步請求,就是在當前線程調用之後直接返回,繼續處理其餘任務,當前調用處理成功之後再經過一個回調線程來處理返回結果。web

舉個作家務的例子類比一下:以前你燒火作飯,就只能守在旁邊添柴加火,等飯作好了才能去擺放餐具;如今你買了一個電飯鍋,當你向電飯鍋添加了作飯必備的材料並下達了作飯的指令之後,電飯鍋就本身開始工做了,你就能夠抽身去擺放餐具了,等飯作好,電飯鍋會主動蜂鳴通知你來取飯,這樣作飯的效率就提升了不少,你的並行處理能力直線上升。spring

Spring MVC 3.2 之後的版本開始引入了基於 Servlet 3 的異步請求處理,能夠實現以下的異步調用。後端

WebAsyncTask-異步調用

Web請求同步與異步調用對比

下面經過一個簡單的同步和異步對比的例子,來快速演示WebAsyncTask的用法以及同步與異步調用的差別。瀏覽器

兩個任務

首先定義一個Controller,並添加了兩個方法來表明一次Web請求要進行的兩個任務,這兩個任務分別要執行3秒。springboot

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/** * WebAsyncTask * * @author ijiangtao * @create 2019-07-03 11:31 **/
@RestController
public class WebAsyncTaskController {

    private Map<String, String> buildResult() {
        System.out.println("building result");
        Map<String, String> result = new HashMap<>();
        try {
            Thread.sleep(3 * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1 * 1000; i++) {
            result.put(i + "-key", i + "value");
        }
        return result;
    }

    private void doTask() {
        System.out.println("do some tasks");
        try {
            Thread.sleep(3 * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

複製代碼

同步執行Web請求

@GetMapping("/r1")
public Map<String, String> r1() {

    Instant now = Instant.now();

    Map<String, String> result = buildResult();

    doTask();

    System.out.println("r1 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");

    return result;
}
複製代碼

在同步執行請求的狀況下,Web請求處理中全部的任務都是並行的,最終的執行耗時是全部任務的總和。服務器

異步執行Web請求

@GetMapping("/r2")
public WebAsyncTask<Map<String, String>> r2() {

    Instant now = Instant.now();

    Callable<Map<String, String>> callable = new Callable<Map<String, String>>() {
        @Override
        public Map<String, String> call() throws Exception {
            return buildResult();
        }
    };

    doTask();

    WebAsyncTask<Map<String, String>> webAsyncTask = new WebAsyncTask<>(callable);

    System.out.println("r2 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");

    return webAsyncTask;
}
複製代碼

對於異步執行的Web請求,咱們經過實現Callable接口的call方法來定義Web請求返回結果的任務,並經過WebAsyncTask來執行任務,當調用任務之後當即返回,便可並行執行其餘任務,最終當WebAsyncTask執行完成之後,Web請求得到返回。

定製異步執行Web請求

若是僅僅想實現異步和並行處理,使用JDK提供的Future機制也能夠實現,WebAsyncTask的魅力還在於提供了超時時間配置、異步任務執行器以及執行完成回調、執行異常和超時後的回調等。

package net.ijiangtao.tech.demo.webasynctask.controller;

import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/** * WebAsyncTask * * @author ijiangtao * @create 2019-07-03 11:31 **/
@RestController
public class WebAsyncTask2Controller {
    
    @GetMapping("/r3")
    public WebAsyncTask<Map<String, String>> r2() {

        Instant now = Instant.now();

        Callable<Map<String, String>> callable = new Callable<Map<String, String>>() {
            @Override
            public Map<String, String> call() throws Exception {
                return buildResult();
            }
        };

        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.setThreadNamePrefix("WebAsyncTask2-");

        WebAsyncTask<Map<String, String>> webAsyncTask = new WebAsyncTask<>(2 * 1000L, executor, callable);

        webAsyncTask.onCompletion(new Runnable() {
            @Override
            public void run() {
                System.out.println("Completion");
            }
        });

        webAsyncTask.onError(new Callable<Map<String, String>>() {
            @Override
            public Map<String, String> call() throws Exception {
                System.out.println("Error");
                return new HashMap<>();
            }
        });

        webAsyncTask.onTimeout(new Callable<Map<String, String>>() {
            @Override
            public Map<String, String> call() throws Exception {
                System.out.println("Timeout");
                Map<String, String> timeOutResutl = new HashMap<>();
                timeOutResutl.put("timeout", "result");
                return timeOutResutl;
            }
        });

        doTask();

        System.out.println("r2 time consumption: " + ChronoUnit.SECONDS.between(now, Instant.now()) + " seconds");

        return webAsyncTask;
    }


    private Map<String, String> buildResult() {
        System.out.println("building result");
        Map<String, String> result = new HashMap<>();
        try {
            Thread.sleep(3 * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1 * 1000; i++) {
            result.put(i + "-key", i + "value");
        }
        return result;
    }

    private void doTask() {
        System.out.println("do some tasks");
        try {
            Thread.sleep(3 * 1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

複製代碼

代碼中的例子演示了超時onTimeout之後的處理機制。

WebAsyncTask-異步調用

總結

本文講解了WebAsyncTask實現機制,WebAsyncTask實現了Web請求的異步調用,主要目的是釋放容器線程,提升服務器的吞吐量,在大流量、高併發場景下 (如搶購等) 十分實用。


相關連接

相關資源

本文示例Github源碼

參考文章

SpringBoot WebAsyncTask

異步調用【WebAsyncTask】


Wechat-westcall
相關文章
相關標籤/搜索