iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Software Development

學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構系列 第 3

[ Day 3 ] 為什麼伺服器會卡死?圖解同步、非同步與阻塞的愛恨情仇

  • 分享至 

  • xImage
  •  

前兩天我們聊了系統設計的思維,以及高併發情境下(如演唱會搶票)伺服器連線池被榨乾的慘況。今天,我們終於要切入正題,來面對那個讓無數後端工程師頭痛的終極殺手——高併發下的伺服器卡死問題

要解決連線池乾涸的問題,我們必須先看懂底層的連線模型。很多新手常把「同步/非同步」與「阻塞/非阻塞」混為一談,今天我們就用一間速食店的日常,把這些底層 I/O 邏輯一次講清楚。

同步 (Synchronous) I/O:傳統的速食店櫃台

大部分新手工程師寫出來的第一支 API,底層通常都是同步模型(例如傳統的 Apache HTTP Client 或 Tomcat 預設機制)。這適合用在簡單的順序操作、或對效能要求不高的內部系統。

  • 一個請求連線 (Request)= 一個執行緒(Thread) : Socket 連線建立後,一個使用者的請求就會死死綁定一個伺服器執行緒 (Thread)
  • 阻塞式 (Blocking) 執行: 當伺服器轉發上傳請求給 AWS S3 時,底層的 read()write() 操作會阻塞執行緒。這就像店員把訂單交給廚房後,只能站在櫃台發呆,等待 S3 的回應。這段期間,後面排隊的客人只能乾等。
    https://ithelp.ithome.com.tw/upload/images/20260828/20183864sdHKFcqosn.jpg
  • 優點: 程式碼邏輯非常直觀,由上到下一行一行執行,好寫又好除錯。
  • 缺點: 若有 10,000 個人同時上傳照片,伺服器就需要 10,000 個 Threads。當CPU 和 Memory 有限時, Thread Pool(連線池)被耗盡,第 10,001 個請求就會直接 Timeout。導致執行緒飢餓 (Thread Starvation)。

非同步 (Asynchronous) I/O:拿號碼牌的叫號餐廳

為了突破硬體極限,現代的高併發框架(如 Netty、Node.js 或 Go)幾乎都採用了非同步模型來打造高吞吐量應用。

  • 非阻塞式 (Non-blocking): 店員發送點餐請求後「立即返回」,絕對不傻等。
  • 事件驅動 (Event-driven): 底層會透過 Selector 監控所有連線的 I/O 狀態。執行緒立刻被釋放去幫下一個客人點餐。
  • 回呼通知 (Callback): 當 S3 處理完畢回傳結果時,系統會透過事件迴圈 (Event Loop) 或 Future/CompletableFuture 機制「主動通知」執行緒來接手後續送餐的動作。

https://ithelp.ithome.com.tw/upload/images/20260828/20183864fxKgi8tFXf.jpg

  • 優點: 極致的資源利用率!只需要極少量的 Threads(通常等於 CPU 核心數),就能輕鬆處理數萬個併發連線。伺服器再也不會因為等待而卡死了。
  • 缺點: 程式碼的複雜度大幅上升。執行的順序不再是線性的,工程師必須處理 Callback Hell 或是妥善運用 Future/Promise,除錯難度較高。

總結 : 沒有完美的架構,只有最適合的取捨

https://ithelp.ithome.com.tw/upload/images/20260828/2018386458pkDgywIM.png
(圖片來源:touch1973@Github)
透過這張時間軸,可以清晰地看出三種處理模型的差異:

  • 同步 (單店員死等): 店員把薯條丟進油鍋後,就站在原地發呆,時間軸上有大段的空白(資源閒置)。
  • 非同步 (單店員狂接單): 同一個店員,丟下薯條後立刻轉身去倒飲料、煎肉排。時間軸被塞得滿滿的,完全榨乾單一執行緒的價值。
  • 多執行緒並行 (多個店員): 既然一個店員會卡住,那我就花錢請三個店員,分別負責點餐、炸區和煎區。這就是大家常聽到利用多緒來平行處理。

既然非同步很好,多執行緒更好,為什麼我們不用多執行緒就好?

因為「請員工是要付薪水的」。在伺服器中,每開啟一個 Thread,作業系統就需要分配獨立的記憶體空間給它。當執行緒數量一多,CPU 必須頻繁地在不同執行緒之間切換(Context Switch 上下文切換),這就像三個店員在狹窄的廚房裡不斷互撞、交接工作,反而會拖慢整體速度。

此外當我們要面對上萬個瞬間湧入的併發請求時,同步模型雖然會遇到連線池被榨乾、Thread 佔用太多記憶體的瓶頸,但是非同步或是多緒也會遇到太多threads 讓伺服器記憶體瞬間爆炸 (OOM)。這也是為什麼現代高併發架構都會走向「極少量的執行緒 + 非同步非阻塞機制」的原因。

明天,將帶大家實作傳統的同步連線方式,以及解釋執行緒飢餓(Thread Starvation)!

參考資料

https://ouch1978.github.io/blog/2022/09/25/understand-sync-async-and-multi-thread-with-one-pic#google_vignette


上一篇
[ Day 2 ] 理想與現實的落差:大型系統到底在考量什麼?
下一篇
[ Day 4 ] 傳統的極限:實作同步連線與執行緒飢餓(Thread Starvation)
系列文
學校沒教的後端生存指南:30 天打造非同步 S3 微服務,部署 K8s 實現 HA 架構5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言