webflux Concurrency Model 並發模型
spring mvc和webflux都支持注解controller開發,但是底層的並發模型完全不一樣,對線程阻塞的預期假設也不一樣。
spring mvc或者說servlet應用里邊,servlet是單例多線程模型,是假定業務代碼要阻塞工作線程的,例如業務代碼里向遠程服務發起了一個同步阻塞的http調用。所以servlet容器需要使用一個大線程池去處理並發的請求、應對這種潛在的阻塞。因為不知道哪個請求對應的處理過程中會有這種阻塞,所以線程池小了的話,都被阻塞了、就沒有空閑線程去用了。
而在webflux里邊、或者說non-blocking服務器,我們是預期或者說要求業務代碼都不應該是阻塞的。因此,non-blocking服務器都是用一個小的、固定數目線程的線程池去處理請求。這里邊的線程,也稱為event loop worker。
|
系統的可伸縮型scale和“小且fixed-size線程池”聽起來有些矛盾,但是其實如果能保證絕對不阻塞線程來獲得結果、而是依靠回調來獲得結果,那么我們其實是不需要額外的線程的,因為cpu並發本質上是分配時間片給各個線程,各個線程來回切換來執行,而效率最高的方式顯然是並行,每個cpu負責一個線程。我們是因為線程處理過程中需要阻塞,這時候cpu等着沒事干,才多分配幾個線程,好讓cpu忙起來,提高其利用率的。
|
如果確實需要使用阻塞庫咋辦?Reactor和RxJava都提供了publishOn方法,可以啟動一個額外的線程異步處理業務邏輯。這招雖然簡單,但是要記住,一但真的有這種情況了,說明你技術選型的時候可能不應該或者說至少不一定要選webflux,阻塞API庫不適合webflux這種並發模型。
關於線程安全性,在Reactor和RxJava里邊,都是通過operator來聲明業務邏輯的。在運行的時候,數據都是在反應式pipeline里邊、分階段的進行串行處理的。所以說應用程序員不需要去考慮線程安全問題、不需要去想辦法保護共享資源(mutable state),因為pipeline中的業務代碼不會同時被多個線程並發的調用。
關於線程模型,在一個跑着webflux的server上有哪些主要的線程呢?
1、一個普通的、比如沒有什么data access或者其他的類似依賴的webflux server上邊,會有一個server線程,以及cpu核心數個請求處理線程。而servlet容器就不一樣了:線程數要多得多,像tomcat這種默認最大線程數來到了200個,因為它要同時支持servlet blocking IO和servlet3.1的non-blocking IO。
2、WebClient也是用的event loop方式:少量的幾個名字“reactor-http-nio- xx”的這種線程來負責發送和讀請求的返回。但是如果在一個webflux應用里邊,同時有WebServer模塊的服務端和WebClient的客戶端,並且底層都是用的Reactor Netty的話,那么這兩者是共用event loop資源的。
3、Reactor和RxJava提供了線程池的抽象————schedulers,使用publishOn方法把業務代碼丟給一個額外異步線程池————scheduler去處理、而不在event loop里處理了。
類似於JDK里邊的Executors類里邊的那幾個靜態方法:cacheThreadPool、fixThreadPool、balabala。。。scheduler也有不同的並發策略,適合不同的場景:
“parallel”,需求少量的幾個線程、cpu計算密集型場景;
“elastic”,需求大量的線程、IO密集型場景。
所以如果你瞅見了上述這類名字的線程,意味着當前這個應用使用了Scheduler啟了這些策略的異步線程池。
4、Data access庫和其他第三方的依賴可以創建並使用它們自己的線程池。
加了一些自己的理解。
