iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Software Development

Spring Boot + Kotlin 協程高併發,後端開發新選擇系列 第 15 篇

Day 14:小結:三種技術選型的判斷依據,MVC、WebFlux 各用在哪

  • 分享至 

  • xImage
  •  

Day 13:協程搭配訊息佇列與外部 API 呼叫的常見模式 結尾把問題攤開來了:這幾天累積的技術選型各自看起來都理解了,但實務上該根據什麼判斷依據去選擇,還沒有一個統一的答案。今天要把這張選型地圖畫出來。

四天之內,這個系列陸續走過三個技術選項。

Day 10:在 Spring MVC 裡寫 suspend function,能動但有代價 定案了協程與 Spring MVC 整合模式,語法上可行,但底層仍是 Thread-per-Request 容器骨架。

Day 11:WebFlux 是什麼,協程與反應式模型的分工 定案了 WebFlux 反應式模型,少數事件循環執行緒服務大量連線,與協程分屬不同層次,可以互補搭配。

Day 12:R2DBC,讓資料庫存取也不阻塞執行緒 定案了 R2DBC,把非阻塞這件事延伸到資料庫存取這一層,全系列資料庫技術棧也在那天定案為 PostgreSQL 搭配 R2DBC。

今天不會重新解釋這三者各自怎麼運作,那些定義已經定案,直接沿用即可。今天要處理的問題更實際:這三者不是互斥的三選一,而是分屬不同層次的選擇。Web 框架這個層級,要在 Spring MVC 與 WebFlux 之間選一個;資料庫存取這個層級,則有 R2DBC 這個非阻塞選項可以搭配。把這兩層分開看,選型才不會混成一團,本篇就是要建立這個清楚的判斷框架。

團隊與既有系統的現況,往往比技術優劣更早決定答案

選型這件事,很容易被簡化成「哪個技術比較先進、比較該選」。但實務上,第一個要面對的問題從來不是這個,而是團隊手上現在有什麼、熟悉什麼。

如果團隊已經有大量既有的 Spring MVC 專案,維運經驗也都建立在這套框架之上,短期內沒有更換底層框架的迫切需求,Day 10 提到的「在 Spring MVC 裡寫 suspend function」就是務實的起點。

這個選擇不會讓系統立刻擺脫 Thread-per-Request 執行緒池上限的限制,但可以先讓新寫的業務邏輯採用協程風格,享受程式碼一致性帶來的維護效益,之後如果真的評估要往非阻塞架構遷移,業務邏輯層已經是協程寫法,遷移阻力會小很多。這是一條漸進路線,不是終點,但很多團隊的現實條件就是只能走漸進路線。

如果是全新專案,或者專案雖然還小,但已經明確預期會面對高併發流量情境,情況就不一樣了。

這種時候與其先走過渡方案再回頭遷移,不如直接採用 WebFlux 搭配 R2DBC,從底層就是非阻塞的組合,長期來看能更完整發揮協程與非阻塞模型疊加起來的效益。少了既有系統的包袱,一開始就照最終想要的架構去搭,通常比中途轉向划算。

團隊熟悉度也是同一個維度底下的變數。

WebFlux 搭配 R2DBC 的除錯經驗與傳統 Spring MVC 相比,思維方式差異不小。事件循環模型下的堆疊追蹤與例外處理,不像 Thread-per-Request 模型那樣直覺。

如果團隊完全沒有反應式程式設計的經驗,貿然把正式環境的核心服務整套換成 WebFlux,風險不只在架構本身,也在團隊是否有能力在半夜出狀況時看懂發生了什麼事。這一點不會讓答案反轉,但會影響選型時該投入多少學習與觀察的緩衝期。

併發規模與等待密集程度,決定選型的迫切性

現況與熟悉度講完了,換一個角度看,從技術特性本身出發,兩個變數會直接影響選型帶來的效益差異有多明顯:併發規模,以及請求處理過程中等待外部回應的密集程度。

先看併發規模不大的情境。

回到 Day 02:Thread-per-Request 模型的極限,搞懂你原本在用什麼 與 Day 04:小結:把 Thread 模型與協程模型放在一起比一次 已經建立的對照,如果應用程式面對的同時請求數量本來就不多,執行緒池不太可能被打滿,Thread-per-Request 模型底下即使不特別採用協程或 WebFlux,也不一定會遇到明顯瓶頸。

同樣地,如果請求處理過程中大部分時間都花在運算本身,而不是在等資料庫或外部服務回應,非阻塞帶來的效益也有限,畢竟非阻塞解決的是「等待期間執行緒被閒置」這個問題,運算密集的請求本來就沒有多少空等的時間可以省。這種情境下,選型的急迫性偏低,先求穩、先求團隊上手,是合理的優先順序。

再看另一端。回到 Day 11:WebFlux 是什麼,協程與反應式模型的分工 建立的事件循環模型,如果應用程式面對的是高併發流量,而且請求處理過程中大部分時間是在等待資料庫或外部服務回應,這正是 WebFlux 搭配協程與 R2DBC 這個組合最能發揮優勢的情境。示範情境訂單查詢與扣庫存服務就是典型例子,查詢訂單要等資料庫回應,扣庫存要等交易完成,Day 13:協程搭配訊息佇列與外部 API 呼叫的常見模式 提到的確認優惠資格與發送通知,也都是在等外部服務的回應。整個請求生命週期裡,真正在做運算的時間占比很低,絕大部分時間都在等待,這正是少數事件循環執行緒能夠發揮槓桿效益的情境,一條執行緒不需要為了陪一個請求空等,可以轉頭去處理其他連線上真正有事情發生的事件。

把這兩個變數放在一起看,會發現它們往往同時出現,也往往同時缺席。等待密集型的服務,例如電商後端這種大量依賴資料庫與外部服務的系統,通常也正是併發規模容易在促銷活動期間急遽上升的服務。而運算密集型的服務,例如影像處理或複雜的批次計算,就算併發數量高,非阻塞架構能省下的等待時間也相對有限,這種情境下花力氣換架構,投資報酬率不見得划算。

一張選型對照表,把判斷依據收斂成能直接查閱的整理

前面兩節分別從現況面與技術特性面拆解了判斷依據,這裡把它們收斂成一張表,方便日後查閱與引用。

既有系統狀況 團隊熟悉度 併發規模與等待密集程度 推薦技術組合
大量既有 Spring MVC 專案,短期不換框架 熟悉傳統 Servlet 開發,反應式經驗有限 併發規模不大,或請求以運算為主 Spring MVC 搭配 suspend function 過渡方案
大量既有 Spring MVC 專案,但計畫逐步遷移 團隊願意投入學習,先從業務邏輯層開始鋪路 目前規模尚可,但預期未來會升高 Spring MVC 搭配 suspend function,業務邏輯先協程化,作為遷移前置準備
全新專案,沒有既有框架包袱 具備或願意建立反應式除錯經驗 高併發,且請求大部分時間在等待資料庫或外部服務 WebFlux 搭配協程與 R2DBC
全新專案,團隊完全沒有反應式經驗 尚未建立反應式除錯經驗,短期內難以補足 高併發,等待密集 先以 Spring MVC 搭配 suspend function 起步,同步建立團隊反應式能力,再評估遷移時機

這張表沒有標準答案可以套用到所有團隊,但它把「該問自己哪些問題」整理清楚了。既有系統與團隊熟悉度決定了現實可行性,併發規模與等待密集程度決定了技術效益的迫切性,兩者交叉比對,答案通常會自然浮現,不需要陷入非黑即白的技術信仰之爭。

這裡也順帶把後續章節的背景交代清楚。這個系列從 《Day 22:收斂進一個具名專案,訂單服務實戰整合版啟動》 起進入實戰整合期,示範專案訂單服務實戰整合版會採用 WebFlux 搭配協程與 R2DBC 這個組合作為主軸,理由正好對應這張表的右下角,示範情境是全新設計、高併發、等待密集的服務,最適合從底層就是非阻塞的組合。後面章節看到的程式碼範例,背景都建立在這個選型之上,不會再回頭示範 Spring MVC 過渡方案的寫法,這裡先說清楚,避免讀者在後續章節看到具體技術棧時感到突兀。

框架選好了,但真正的挑戰才要開始

Day 10:在 Spring MVC 裡寫 suspend function,能動但有代價 到今天這五天,處理的是「用什麼底層架構」這個選型問題。選定架構之後,程式碼確實可以用協程風格撰寫,資料庫存取也確實具備非阻塞的潛力,這是生態整合期累積下來紮紮實實的成果。

但選對框架,不代表系統就能自動撐住任何規模的流量。

即使底層是 WebFlux 搭配 R2DBC 這種從骨子裡就非阻塞的組合,如果同時湧入的請求量大到連下游資料庫或外部服務都吃不消,架構本身的非阻塞特性並不會自動幫忙節流、也不會自動處理某個依賴突然回應緩慢的情況。

這些問題目前為止都還沒有正面處理過,前面四天談的一直是「怎麼讓執行緒不被白白浪費」,還沒有談到「當真正大量的請求同時湧入時,該怎麼保護系統不被壓垮」。

這是一個新的命題,跟前面五天討論的內容站在不同的問題層次上。下一篇會正式進入併發深化期,開始處理這些真正的高併發控制問題。

Day 13:協程搭配訊息佇列與外部 API 呼叫的常見模式 把生態整合期最後一塊拼圖擺上桌,今天則把整個階段收斂成一張選型地圖,也在此為生態整合期畫下句點。《Day 15:併發限制,用 Semaphore 保護下游別被打爆》 見。


上一篇
Day 13:協程搭配訊息佇列與外部 API 呼叫的常見模式
系列文
Spring Boot + Kotlin 協程高併發,後端開發新選擇 共 15 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言