你應該知道的幾種Spring boot非同步呼叫方式
在工作中,我們可能會遇到一些需要非同步呼叫的場景。如果你接手的是一個維護的專案,大機率這部分內容都是已經存在的。但是如果你需要搭建新的專案,非同步的功能就不可或缺了。
Springboot專案的非同步呼叫大概分為一下幾種:
一、使用XXL-JOB等類似的框架
1.1 簡介
XXL-JOB 是一個分散式任務排程平臺,旨在解決大規模分散式系統中的任務排程和管理問題。它支援定時任務的排程和執行,支援多種執行方式(如 cron 表示式、簡單定時、一次性任務等),並且具有高可用、容錯、動態配置、任務分片等特性,廣泛應用於微服務、分散式系統等場景。
1.2 主要特性
- Web 控制檯:提供圖形化介面來管理任務,包括定時任務的建立、修改、排程、監控等。
- 分散式排程:支援叢集部署,透過任務分片和負載均衡實現任務的分散式執行。
- 高可用和容錯:支援任務的失敗重試,節點故障自動轉移。
- 任務監控:提供實時任務執行情況、日誌檢視、報警等功能,幫助開發者監控任務執行。
- 多語言支援:支援 Java、Shell、Python 等多種語言的任務執行。
1.3 典型架構
- Admin:排程中心,負責排程管理、任務註冊、執行日誌記錄等。
- Executor:任務執行器,負責實際的任務執行。
- Client:用於任務的呼叫,通常為應用服務。
1.4 簡單示例
- 配置排程中心:啟動 XXL-JOB Admin。
- 任務執行器:在 Spring Boot 專案中整合 XXL-JOB,使用註解 @XxlJob 來定義任務。
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.context.XxlJobContext;
import com.xxl.job.core.biz.model.ReturnT;
import org.springframework.stereotype.Component;
@Component
public class DemoJobHandler {
// 1. 使用 Cron 表示式配置任務
@XxlJob(value = "cronJobHandler", cron = "0 0 12 * * ?") // 每天12點執行
public ReturnT<String> cronJobHandler(String param) throws Exception {
System.out.println("Cron job executed at 12:00.");
return ReturnT.SUCCESS;
}
// 2. 使用簡單定時配置任務
@XxlJob(value = "simpleJobHandler", cron = "0/10 * * * * ?") // 每10秒執行一次
public ReturnT<String> simpleJobHandler(String param) throws Exception {
System.out.println("Simple job executed every 10 seconds.");
return ReturnT.SUCCESS;
}
// 3. 一次性任務(無需Cron設定,手動觸發)
@XxlJob("onceJobHandler") // 一次性執行
public ReturnT<String> onceJobHandler(String param) throws Exception {
System.out.println("Once job executed only once.");
return ReturnT.SUCCESS;
}
}
1.5 總結
XXL-JOB 是一個功能強大且靈活的任務排程平臺,適合需要高可用、高併發任務排程的場景,尤其在分散式和微服務架構中表現優秀。
二、ThreadPoolTaskExecutor 結合@Async
2.1 簡介
ThreadPoolTaskExecutor 是 Spring 提供的一個常用執行緒池實現,它實現了 TaskExecutor 介面,允許你在 Spring 應用中靈活地配置和管理執行緒池。它適合用於需要控制執行緒池大小和執行緒行為的場景,可以與 @Async 配合使用來管理非同步任務的執行。
2.2 主要特性與配置項
- 執行緒池管理
ThreadPoolTaskExecutor 基於執行緒池進行任務執行,可以管理執行緒的建立、銷燬、排程等操作,避免了頻繁建立新執行緒的開銷,提高了應用效能。 - 核心執行緒數(Core Pool Size)
核心執行緒數是執行緒池中始終存在的執行緒數,即使這些執行緒處於空閒狀態也會保留在池中。ThreadPoolTaskExecutor 允許配置核心執行緒數,從而控制池中最小的執行緒數。 - 配置項:setCorePoolSize(int corePoolSize)
預設值:1 - 最大執行緒數(Max Pool Size)
最大執行緒數是執行緒池可以容納的最大執行緒數。當執行緒池中所有的核心執行緒都在執行任務時,如果佇列已滿,則可以建立新執行緒來執行任務,直到達到最大執行緒數。超過最大執行緒數的任務將被拒絕。 - 配置項:setMaxPoolSize(int maxPoolSize)
預設值:Integer.MAX_VALUE - 執行緒空閒時間(Keep-Alive Time)
執行緒池中空閒執行緒的最大空閒時間。如果某個執行緒在指定的空閒時間內沒有執行任務,它將被銷燬,直到執行緒數達到核心執行緒數為止。 - 配置項:setKeepAliveSeconds(int keepAliveSeconds)
預設值:60(秒) - 任務佇列(Queue Capacity)
執行緒池中的任務佇列用於儲存等待執行的任務。當執行緒池中的執行緒數達到核心執行緒數時,新的任務將被放入佇列等待執行。任務佇列的容量決定了能夠同時排隊等待的任務數量。 - 配置項:setQueueCapacity(int queueCapacity)
預設值:Integer.MAX_VALUE - 拒絕策略(RejectedExecutionHandler)
當執行緒池中的執行緒數達到最大執行緒數,且佇列已滿時,執行緒池將根據設定的拒絕策略來處理新提交的任務。ThreadPoolTaskExecutor 提供了多種拒絕策略:- CallerRunsPolicy:呼叫者執行緒執行該任務。
- AbortPolicy:直接丟擲 RejectedExecutionException 異常,預設策略。
- DiscardPolicy:丟棄任務,不丟擲異常。
- DiscardOldestPolicy:丟棄最舊的任務,執行新的任務。
- 配置項:setRejectedExecutionHandler(RejectedExecutionHandler handler)
預設值:AbortPolicy - 執行緒名稱字首(Thread Name Prefix)
ThreadPoolTaskExecutor 允許為池中的執行緒指定一個名稱字首,以便在日誌或監控中輕鬆識別。 - 配置項:setThreadNamePrefix(String threadNamePrefix)
預設值:無 - 是否允許核心執行緒超時(Allow Core Thread TimeOut)
預設情況下,核心執行緒是不會被銷燬的。設定為 true 後,核心執行緒也可以在空閒超時後被銷燬。 - 配置項:setAllowCoreThreadTimeOut(boolean allowCoreThreadTimeOut)
預設值:false - 初始化/銷燬鉤子(Lifecycle Hooks)
ThreadPoolTaskExecutor 實現了 InitializingBean 和 DisposableBean 介面,可以在初始化和銷燬時執行特定操作。afterPropertiesSet() 會線上程池初始化時被呼叫,destroy() 在銷燬時被呼叫。 - 非同步任務執行(TaskExecutor 介面)
ThreadPoolTaskExecutor 實現了 Spring 的 TaskExecutor 介面,這使得它可以在 Spring 的非同步任務執行環境中使用,例如,@Async 註解的非同步方法。
2.3 簡單示例
- 配置
- setCorePoolSize(int): 設定核心執行緒數。
- setMaxPoolSize(int): 設定最大執行緒數。
- setQueueCapacity(int): 設定佇列容量,用於儲存等待執行的任務。
- setThreadNamePrefix(String): 設定執行緒名稱的字首。
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心執行緒數
executor.setMaxPoolSize(10); // 最大執行緒數
executor.setQueueCapacity(25); // 佇列容量
executor.setThreadNamePrefix("MyExecutor-"); // 執行緒名稱字首
executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任務完成
executor.setKeepAliveSeconds(60); // 執行緒保持活動的時間
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒絕策略
return executor;
}
- 用法
@Service
public class AsyncService {
@Async("taskExecutor") // 使用自定義執行緒池
public void performTask() {
System.out.println("Executing task in thread: " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
三、ListenableFuture
3.1 簡介
ListenableFuture 是 Guava 提供的一個介面,擴充套件了 java.util.concurrent.Future,主要用於非同步程式設計。與 Future 不同,ListenableFuture 支援註冊回撥方法,在任務執行完畢時自動執行,而無需顯式地呼叫 get() 方法來獲取結果。
3.2 主要特性
- 回撥機制:可以透過 addListener() 方法為任務新增回撥函式,這些回撥會在任務完成後被執行。
- 支援取消:與 Future 一樣,支援取消任務(cancel())。
- 支援非同步結果:可以透過 get() 或 get(long timeout, TimeUnit unit) 獲取任務結果,但通常建議透過回撥來處理結果,避免阻塞。
- 非同步處理:可以與 ListeningExecutorService 配合使用,使得任務執行和結果處理完全非同步。
3.3 核心方法
- addListener(Runnable listener, Executor executor):為任務新增回撥方法,任務執行完成時會呼叫回撥。
- get():阻塞方法,等待任務執行完成並返回結果。
- get(long timeout, TimeUnit unit):帶有超時限制的阻塞方法。
3.4 簡單示例
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ListenableFutureExample {
public static void main(String[] args) throws Exception {
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));
ListenableFuture<Integer> future = executorService.submit(() -> {
TimeUnit.SECONDS.sleep(2);
return 42; // 任務返回的結果
});
// 註冊回撥函式
future.addListener(() -> {
try {
System.out.println("Result: " + future.get()); // 獲取結果
} catch (Exception e) {
e.printStackTrace();
}
}, executorService);
// 其他操作
System.out.println("Task submitted.");
}
}
3.5 總結
ListenableFuture 提供了更靈活的非同步程式設計介面,尤其適合需要非同步回撥處理的場景。透過與 ListeningExecutorService 配合使用,能夠實現任務的非阻塞執行和結果處理。
四、使用非同步 Web 請求處理
4.1 簡介
DeferredResult 是 Spring MVC 提供的一種非同步請求處理機制,可以幫助我們在 Web 請求處理中非同步地返回結果,適用於需要處理時間較長或耗時操作的場景。透過 DeferredResult,Spring MVC 可以將請求處理的執行緒釋放給容器進行其他操作,直到業務邏輯處理完畢後再將結果返回給客戶端。這個特性特別適合那些需要等待外部系統呼叫(如遠端服務、資料庫等)返回結果的場景。
4.2 DeferredResult 的工作原理
- 非同步處理: 當請求到達時,Spring MVC 將請求交給一個非同步處理執行緒。在處理的過程中,Web 請求執行緒並不會被阻塞,可以處理其他請求。
- 呼叫 setResult: 當業務邏輯處理完畢後,我們透過呼叫 DeferredResult 的 setResult 或 setErrorResult 方法將處理結果返回給客戶端,通知客戶端請求已完成。
- 超時控制: 可以設定超時時間,如果請求在規定時間內未完成,可以設定超時處理。
4.3 典型的使用場景
- 呼叫遠端服務(例如微服務間通訊)。
- 執行長時間的資料庫操作。
- 等待外部事件或訊息。
4.4 基本步驟
- 使用 @RequestMapping 或 @GetMapping 等註解標註一個控制器方法,返回型別為 DeferredResult。
- 在方法內部處理非同步業務邏輯,並在業務完成後呼叫 DeferredResult.setResult() 或 DeferredResult.setErrorResult()。
4.5 示例程式碼
假設我們有一個非同步服務,它會模擬一個延遲操作,例如模擬呼叫一個遠端服務,然後返回資料。
- 控制器實現非同步處理
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@RestController
@EnableAsync // 啟用非同步支援
public class AsyncController {
@Autowired
private AsyncService asyncService;
// 返回 DeferredResult,表示非同步請求
@GetMapping("/async")
public DeferredResult<String> handleAsyncRequest() {
// 建立 DeferredResult 物件,設定超時時間
DeferredResult<String> deferredResult = new DeferredResult<>(5000L, "Timeout occurred");
// 呼叫非同步方法處理
asyncService.processAsyncTask(deferredResult);
// 返回 DeferredResult
return deferredResult;
}
}
- 非同步服務處理
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.TimeUnit;
@Service
public class AsyncService {
@Async
public void processAsyncTask(DeferredResult<String> deferredResult) {
try {
// 模擬耗時操作,如外部服務呼叫
TimeUnit.SECONDS.sleep(3); // 延遲3秒
// 操作完成後,呼叫 setResult 返回結果
deferredResult.setResult("Task Completed Successfully!");
} catch (InterruptedException e) {
// 錯誤處理,呼叫 setErrorResult
deferredResult.setErrorResult("Error Occurred");
}
}
}
- 配置非同步請求支援
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 配置非同步超時時間
configurer.setDefaultTimeout(5000L);
}
}
4.6 重要方法解析
- setResult(T result):當非同步操作完成並準備好響應時呼叫該方法,傳遞結果資料給客戶端。
- setErrorResult(Object errorResult):如果非同步操作出現錯誤,可以呼叫此方法傳遞錯誤資訊。
- DeferredResult(long timeout, Object timeoutResult):可以設定超時處理。如果請求在指定時間內未完成,將返回一個預設的超時結果。
例如,DeferredResultdeferredResult = new DeferredResult<>(5000L, "Timeout occurred"); 表示請求的超時時間為 5 秒,超時後將返回 "Timeout occurred"。 - getResult():可以在後續處理中獲取處理結果,但一般推薦在非同步回撥方法中處理結果。
4.7 異常處理
可以在非同步操作中捕獲異常,並使用 setErrorResult 返回錯誤資訊。
try {
// 模擬耗時操作
TimeUnit.SECONDS.sleep(3);
deferredResult.setResult("Success");
} catch (Exception e) {
deferredResult.setErrorResult("Error: " + e.getMessage());
}
4.8 配置超時
DeferredResult<String> deferredResult = new DeferredResult<>(5000L, "Timeout Occurred");
// 這會設定5秒的超時時間,超時後返回 "Timeout Occurred"。
4.9 結合 @Async 實現更復雜的非同步邏輯
Spring 支援使用 @Async 註解來簡化非同步操作。透過 @Async 可以讓方法在獨立的執行緒中執行,從而不會阻塞主執行緒。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
@Service
public class AsyncService {
@Async
public void processAsyncTask(DeferredResult<String> deferredResult) {
try {
// 模擬長時間處理
Thread.sleep(5000);
deferredResult.setResult("Processing Complete");
} catch (InterruptedException e) {
deferredResult.setErrorResult("Error occurred during processing");
}
}
}
4.10 總結
- DeferredResult 是 Spring MVC 提供的用於處理非同步請求的工具。它能夠讓請求不再阻塞主執行緒,處理完成後再透過 setResult 或 setErrorResult 返回結果或錯誤。
- @Async 和 DeferredResult 配合使用,可以更容易地處理需要長時間等待的非同步請求,避免阻塞請求執行緒,提高應用的響應能力。
- 配置超時和錯誤處理,確保在非同步操作中出現問題時能夠及時響應。
這種機制對於需要高效能和響應快速的 Web 應用非常有用,特別是當涉及到遠端服務呼叫、複雜的計算任務或耗時操作時。