環(huán)境:SpringBoot2.7.12
1. 概述
在現(xiàn)代的互聯(lián)網(wǎng)應(yīng)用中,隨著用戶數(shù)量的不斷增加和業(yè)務(wù)復(fù)雜性的提升,并發(fā)問題成為了開發(fā)中面臨的重大挑戰(zhàn)。傳統(tǒng)的同步請求接口往往無法滿足高并發(fā)場景的需要,不僅會阻塞調(diào)用線程,影響系統(tǒng)的響應(yīng)性能,而且還可能導(dǎo)致線程資源的浪費。為了解決這些問題,異步請求接口逐漸成為了開發(fā)者的首選。
在SpringBoot框架中,異步請求接口的創(chuàng)建和使用非常方便,能夠讓你輕松解決并發(fā)問題,提高系統(tǒng)的可維護性和響應(yīng)性能。本文將介紹如何快速掌握SpringBoot異步請求接口,以輕松解決并發(fā)問題。
2. 異步請求接口優(yōu)勢
異步請求接口相比于傳統(tǒng)同步請求具有以下優(yōu)勢:
總之,異步請求接口具有上述優(yōu)勢,尤其是在高并發(fā)場景下,能夠提高系統(tǒng)的性能和可用性,是解決并發(fā)問題的有效方法之一。
3. 應(yīng)用場景
異步請求接口可以應(yīng)用于以下場景:
總之,異步請求接口適用于那些需要避免阻塞、提高系統(tǒng)響應(yīng)性能、處理耗時操作和實時數(shù)據(jù)處理等場景中,能夠提高系統(tǒng)的并發(fā)性能和資源利用率,減少系統(tǒng)瓶頸的出現(xiàn)。
接下來我們進入正文,在Spring環(huán)境下如何將我們的接口異步化。
4. 實戰(zhàn)異步接口
Spring MVC 廣泛集成了 Servlet 3.0 異步請求處理功能:
4.1 DeferredResult
一旦在 Servlet 容器中啟用異步請求處理功能,控制器方法就可以用 DeferredResult 封裝任何受支持的控制器方法返回值,如下例所示:
@GetMapping("/deferred")@ResponseBodypublic DeferredResult<Map<String, Object>> deferred(){ long start = System.currentTimeMillis() ; System.out.printf("%s - 開始時間:%d%n", Thread.currentThread().getName(), start) ; DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>(); // 為了演示方便直觀,這里直接創(chuàng)建線程 new Thread(() -> { try { // 這里模擬耗時操作 TimeUnit.SECONDS.sleep(3) ; // 將執(zhí)行結(jié)果保存 Map<String, Object> result = new HashMap<>() ; result.put("code", 1) ; result.put("data", "你的業(yè)務(wù)數(shù)據(jù)") ; deferredResult.setResult(result) ; } catch (InterruptedException e) {} }).start() ; long end = System.currentTimeMillis() ; System.out.printf("%s - 結(jié)束時間:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("總耗時:%d毫秒%n", (end - start)) ; return deferredResult ;}
控制臺輸出結(jié)果:
2023-10-19 14:25:30.321 INFO 3884 --- [nio-8808-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 mshttp-nio-8808-exec-1 - 開始時間:1697696730335http-nio-8808-exec-1 - 結(jié)束時間:1697696730335總耗時:0毫秒
從結(jié)果看出,處理請求的tomcat線程幾乎沒有占用時間,線程被快速的釋放,這樣就可以去處理其它的連接請求,整個系統(tǒng)的吞吐量就的到了明顯的提升。
Controller可以從不同的線程異步生成返回值。
4.2 Callable
Controller可以用 java.util.concurrent.Callable 封裝任何受支持的返回值,如下例所示:
@GetMapping("/callable")public Callable<Map<String, Object>> callable() { long start = System.currentTimeMillis() ; System.out.printf("%s - 開始時間:%d%n", Thread.currentThread().getName(), start) ; Callable<Map<String, Object>> callable = new Callable<Map<String, Object>>() { public Map<String, Object> call() throws Exception { Map<String, Object> result = new HashMap<>() ; try { // 這里模擬耗時操作 TimeUnit.SECONDS.sleep(3) ; // 將執(zhí)行結(jié)果保存 result.put("code", 1) ; result.put("data", "你的業(yè)務(wù)數(shù)據(jù)") ; } catch (InterruptedException e) {} return result ; } } ; long end = System.currentTimeMillis() ; System.out.printf("%s - 結(jié)束時間:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("總耗時:%d毫秒%n", (end - start)) ; return callable ;}
控制臺輸出結(jié)果:
http-nio-8808-exec-2 - 開始時間:1697697345385http-nio-8808-exec-2 - 結(jié)束時間:1697697345386總耗時:1毫秒
執(zhí)行結(jié)果與上面一樣。
注意:這里Callable中的代碼執(zhí)行是在系統(tǒng)默認的一個TaskExecutor線程池中運行,我們可以通過配置自己的TaskExecutor來執(zhí)行。如下:
@Beanpublic ThreadPoolTaskExecutor myAsyncTaskExecutor() { // 配置ThreadPoolTaskExecutor相關(guān)參數(shù),比如核心線程數(shù)等 return ... ;}
4.3 ResponseBodyEmitter
你可以將 DeferredResult 和 Callable 用于單個異步返回值。如果要生成多個異步值并將其寫入響應(yīng),該怎么辦?本節(jié)將介紹如何做到這一點。
ResponseBodyEmitter 的返回值生成一個對象流,其中每個對象都會被 HttpMessageConverter 序列化并寫入響應(yīng),如下例所示:
// 這里應(yīng)該保存到一個集合中private ResponseBodyEmitter emitter ;@GetMapping("/emitter")public ResponseBodyEmitter emitter() throws Exception { ResponseBodyEmitter bodyEmitter = new ResponseBodyEmitter(-1L); this.emitter = bodyEmitter ; return bodyEmitter;}// 可以不斷調(diào)用該接口進行消息的發(fā)送@GetMapping("/sender")public void sender() throws Exception { this.emitter.send(System.currentTimeMillis()) ;}// 調(diào)用該j接口后請求結(jié)束@GetMapping("/complete")public void complete() throws Exception { this.emitter.complete() ;}
當(dāng)訪問/emitter接口時,瀏覽器會一直轉(zhuǎn)圈,一直等待。只有調(diào)用了/complete接口后請求內(nèi)容才會被發(fā)送到客戶端并結(jié)束請求。
4.4 StreamingResponseBody
有時,繞過消息轉(zhuǎn)換并直接流式傳輸?shù)巾憫?yīng)的 OutputStream(例如,文件下載)非常有用。為此,可以使用 StreamingResponseBody 返回值類型,如下例所示:
@GetMapping("/stream")public ResponseEntity<StreamingResponseBody> stream() { long start = System.currentTimeMillis() ; System.out.printf("%s - 開始時間:%d%n", Thread.currentThread().getName(), start) ; // 內(nèi)部執(zhí)行還是用的系統(tǒng)內(nèi)部的線程池 StreamingResponseBody stream = new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { outputStream.write(String.valueOf("當(dāng)前時間: " + System.currentTimeMillis() + "<br/>").getBytes()) ; try { TimeUnit.SECONDS.sleep(1) ; } catch (InterruptedException e) {} outputStream.write(String.valueOf("當(dāng)前時間: " + System.currentTimeMillis() + "<br/>").getBytes()) ; try { TimeUnit.SECONDS.sleep(1) ; } catch (InterruptedException e) {} outputStream.write(String.valueOf("當(dāng)前時間: " + System.currentTimeMillis() + "<br/>").getBytes()) ; } }; MultiValueMap<String, String> headers = new HttpHeaders() ; headers.add("Content-Type", "text/html;charset=UTF-8") ; ResponseEntity<StreamingResponseBody> response = new ResponseEntity<StreamingResponseBody>(stream, headers , HttpStatus.OK) ; long end = System.currentTimeMillis() ; System.out.printf("%s - 結(jié)束時間:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("總耗時:%d毫秒%n", (end - start)) ; return response ;}
控制臺輸出:
http-nio-8808-exec-1 - 開始時間:1697700256912http-nio-8808-exec-1 - 結(jié)束時間:1697700256915總耗時:3毫秒
tomcat線程非常短的時間內(nèi)釋放,這樣就可以處理更多的請求,提升系統(tǒng)整體的吞吐量。這種最適合文件下載。
瀏覽器輸出:
4.5 SSE
該方式請閱讀《實時數(shù)據(jù)推送并非只有WebSocket一種選擇》詳細介紹了如何使用。
4.6 基于反應(yīng)式
Spring MVC 支持在控制器中使用反應(yīng)式客戶端庫。這包括 spring-webflux 中的 WebClient 以及 Spring Data 反應(yīng)式數(shù)據(jù)存儲庫等其他庫。在這種情況下,從控制器方法中返回反應(yīng)類型是很方便的。如下例所示:
@GetMapping("/mono")public Mono<Map<String, Object>> mono() { long start = System.currentTimeMillis() ; System.out.printf("%s - 開始時間:%d%n", Thread.currentThread().getName(), start) ; Mono<Map<String, Object>> mono = Mono.defer(() -> { try { TimeUnit.SECONDS.sleep(3) ; } catch (InterruptedException e) {} Map<String, Object> result = new HashMap<>() ; result.put("code", 1) ; result.put("data", "你的業(yè)務(wù)數(shù)據(jù)") ; return Mono.just(result) ; }) ; long end = System.currentTimeMillis() ; System.out.printf("%s - 結(jié)束時間:%d%n", Thread.currentThread().getName(), end) ; System.out.printf("總耗時:%d毫秒%n", (end - start)) ; return mono ;}
控制臺輸出:
http-nio-8808-exec-2 - 開始時間:1697700686250http-nio-8808-exec-2 - 結(jié)束時間:1697700686251總耗時:1毫秒
以上就是Spring中異步請求接口的實現(xiàn)方式。
異步請求接口是解決并發(fā)問題的有效方法之一,特別是在高并發(fā)、耗時操作、實時數(shù)據(jù)處理等場景中具有顯著優(yōu)勢。通過異步請求,系統(tǒng)能夠避免阻塞線程,提高系統(tǒng)的響應(yīng)性能和資源利用率。
本文鏈接:http://www.tebozhan.com/showinfo-26-14324-0.html快速掌握Spring異步請求接口,輕松解決并發(fā)問題
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com