我們日常用Spring Boot寫的RestController
,感覺上就是一個簡單的方法,但它背后其實有一套復雜的網絡服務在支撐。一個HTTP請求到底是怎么從用戶的瀏覽器,穿過層層網絡,最終抵達我們代碼里的Controller方法的?理解這個過程,特別是Tomcat和Netty這兩種主流服務器的處理方式,對我們寫出更高性能的應用很有幫助。
Tomcat模型:一個請求,一個線程,簡單直接
我們先聊聊大家最熟悉的Spring MVC on Tomcat組合。
Tomcat的NIO模型,本質上是一種高度優化的“單Reactor多線程”實現。它的內部角色分工很明確:有一小隊Acceptor
線程,專門負責在門口迎接新的TCP連接。連接一旦建立,Acceptor
不會親自服務,而是把這個連接交給Poller
線程。
Poller
線程也只有少數幾個,它像個雷達,不斷掃描所有已連接的通道,看看哪個通道上送來了數據。一旦發現某個連接有數據可讀,Poller
也不會自己去讀寫,而是把這個“可讀”的信號打包成一個任務,扔給一個龐大的Worker
線程池。
接下來就是重頭戲了。Worker
線程池里的一個工作線程會領走這個任務,并且負責到底。這個線程會先去網絡通道里把請求數據讀出來,這個讀取的過程是阻塞的。讀完后,它把字節流解析成我們熟悉的HttpServletRequest
對象,然后請求就進入了Spring的處理流程,最終調用到我們的Controller方法。
這里最關鍵的一點是,我們寫的業務代碼,包括查詢數據庫、調用其他服務等所有操作,全都在這個Worker
線程上同步執行。執行完了,再由這個線程把響應數據寫回給用戶。所以,這種模式的好處是編程模型非常簡單,寫代碼就像寫單機程序一樣,思路很直觀。但缺點也很明顯,系統的并發能力,直接受限于Worker
線程池的大小。
Netty模型:事件驅動,少數線程支撐海量連接
再來看看Spring WebFlux on Netty這個組合,它的玩法就完全不一樣了。
Netty是標準的“主從Reactor多線程”架構。它也有一個專門負責接客的BossGroup
,通常就一個線程,工作很專一,只管接收連接,然后把連接轉手扔給WorkerGroup
。
WorkerGroup
里包含了一組EventLoop
線程,數量通常和CPU核心數差不多。一個連接被分配給某個EventLoop
之后,這個連接的整個生命周期就和這個線程綁定了。從數據讀取、解碼成HttpRequest
對象,再到分發給WebFlux框架,所有事情都由這一個EventLoop
線程親力親為。
當請求最終到達我們的Controller方法時,代碼依然是運行在這個EventLoop
線程上的。這就帶來一個嚴格的約束:絕對不能有任何阻塞操作。因為一旦這個線程被阻塞,它負責的所有其他連接就全都動不了了。
所以,我們必須返回Mono
或Flux
這類響應式類型,并通過異步方式去調用下游。EventLoop
線程在發起數據庫查詢或者RPC調用后,會立即返回去處理其他連接上的事件,而不是原地等待。當下游服務返回結果時,會通過一個回調事件,重新喚醒這個EventLoop
線程,讓它繼續完成后續的數據處理和響應。這種事件驅動的模式,使得極少數的線程就能管理海量的并發連接。
核心差異與如何選擇
為了更直觀地看出差別,我們看一個表格。
特性維度 | Spring MVC on Tomcat | Spring WebFlux on Netty |
---|---|---|
線程模型 | 一個請求一個Worker線程 | 少數I/O線程處理所有連接 |
編程范式 | 同步、阻塞 | 異步、非阻塞 (響應式) |
資源占用 | 線程多,內存開銷大 | 線程少,內存開銷小 |
核心風險 | 線程池耗盡 | 阻塞I/O線程 |
適用場景 | 通用CRUD、CPU密集型業務 | 高并發、I/O密集型業務(如網關) |
這兩種模型的性能差異,在處理I/O密集的場景時會被無限放大。比如,處理1000個并發請求,每個請求都要等待1秒的網絡I/O。在Tomcat里,如果Worker
線程池大小是200,那200個線程會立刻被占滿并阻塞,剩下的800個請求只能排隊。而在Netty里,哪怕只有8個EventLoop
線程,也能輕松應對,因為它們從不等待。
那么,我們到底該怎么選?
其實很簡單,看你的業務場景。如果你的應用是I/O密集型的,比如微服務網關、消息推送中臺,需要用有限的服務器資源應對海量的并發連接,那么Netty/WebFlux是更好的選擇,它能最大化系統吞吐能力。
反過來,如果你的應用是業務邏輯復雜、并發量可控的傳統CRUD系統,那Tomcat/MVC的同步模型會讓開發、調試和排查問題變得簡單直觀得多。在這種場景下,開發效率和可維護性的重要性,往往比壓榨那一點硬件性能更重要。
總的來說,Tomcat用線程池隔離了阻塞,讓編程更簡單;Netty用事件驅動壓榨硬件性能,讓并發更高。兩者沒有絕對的優劣,理解它們背后的設計思想,才能在合適的場景做出正確的選擇。