Zuul1.0和2.0我們該如何選擇?
在今年 5 月中,Netflix 終於開源了它的支持非同步調用模式的 Zuul 網關 2.0 版本,真可謂千呼萬喚始出來。從 Netflix 的官方博文 [附錄 1] 中,我們獲得的信息也比較令人振奮:
The Cloud Gateway team at Netflix runs and operates more than 80 clusters of Zuul 2, sending traffic to about 100 (and growing) backend service clusters which amounts to more than 1 million requests per second.
Netflix 部署了超過 80+ 的 Zuul2 雲網關集群,流量經過 Zuul2 集群被路由到後端超過 100+ 的微服務,且每秒鐘經過 Zuul2 集群的請求超過 100 萬。
Zuul2 看起來很強大,支持非同步高並發 (Zuul1 僅支持同步) 特性看起來很亮眼,那麼我們是否就應該拋棄 Zuul1,開始擁抱 Zuul2 呢?作為架構師,我們不能盲目追時髦,技術的選擇必須基於實踐和理性的分析,基於我之前對 Zuul1 的一線落地實戰經驗,也基於我近期對 Zuul2 的一些調研,我會在本文中對 Zuul1 和 Zuul2 做一個比較客觀的編程模型和優劣分析,同時給出我的個人建議。
Zuul 1.0 編程模型和優劣
Zuul1 設計比較簡單,代碼不多也比較容易讀懂,它本質上就是一個同步 Servlet,採用多線程阻塞模型,如上圖所示 [圖片來自附錄 4]。
同步 Servlet 使用 thread per connection 方式處理請求。簡單講,每來一個請求,Servlet 容器要為該請求分配一個線程專門負責處理這個請求,直到響應返回客戶端這個線程才會被釋放返回容器線程池。如果後台服務調用比較耗時,那麼這個線程就會被阻塞,阻塞期間線程資源被佔用,不能幹其它事情。我們知道 Servlet 容器線程池的大小是有限制的,當前端請求量大,而後台慢服務比較多時,很容易耗盡容器線程池內的線程,造成容器無法接受新的請求,Netflix 為此還專門研發了 Hystrix[附錄 2] 熔斷組件來解決慢服務耗盡資源問題。
注意在上圖 Netflix 給出的場景中,它的後台服務調用也是啟動另外一個 IO 線程來處理的,但是本質上還是阻塞模式,後台 IO 線程在處理的時候,前台容器線程仍然是阻塞的。
同步阻塞模式有利有弊,分析如下圖:
優勢
同步阻塞模式的編程模型比較簡單,整個請求 ->處理 ->響應的流程 (call flow) 都是在一個線程中處理的,這樣開發調試比較方便易於理解,比如出了問題跟蹤調試比較方便。另外,線程局部變數 (ThreadLocal) 機制在同步多線程模式下可以工作,有些監控產品,例如 CAT 調用鏈依賴於 ThreadLocal,在同步多線程模式下,CAT 埋點比較方便,調用鏈關係的展示也比較直觀。
不足
我們知道線程本身需要消耗 CPU 和內存資源,且多線程之間切換是有開銷的 (所謂的上下文切換 Context Switch 開銷),線程越多,這種上下文切換的開銷就越大,同步阻塞模式一般會啟動很多的線程,必然引入線程切換開銷。另外,同步阻塞模式下,容器線程池的數量一般是固定的,造成對連接數有一定限制,當後台服務慢,容器線程池易被耗盡,一旦耗盡容器會拒絕新的請求,這個時候容器線程其實並不忙,只是被後台服務調用 IO 阻塞,但是幹不了其它事情。
總體上,同步阻塞模式比較適用於計算密集型 (CPU bound) 應用場景。對於 IO 密集型場景 (IO bound),同步阻塞模式會白白消耗很多線程資源,它們都在等待 IO 的阻塞狀態,沒有做實質性工作。
我在極客時間「微服務架構和實踐 160 講」第三模塊「微服務網關 Zuul 架構和實踐」,對 Zuul 進行了十分詳細的講解,戳此了解詳情
Zuul 2.0 編程模型和優劣
Zuul2 的設計相對比較複雜,代碼也不太容易讀懂,它採用了 Netty 實現非同步非阻塞編程模型,如上圖所示 [圖片來自附錄 4]。
一般非同步模式的本質都是使用隊列 Queue(或稱匯流排 Bus),在上圖中,你可以簡單理解為前端有一個隊列專門負責處理用戶請求,後端有個隊列專門負責處理後台服務調用,中間有個事件環線程 (Event Loop Thread),它同時監聽前後兩個隊列上的事件,有事件就觸發回調函數處理事件。這種模式下需要的線程比較少,基本上每個 CPU 核上只需要一個事件環處理線程,前端的連接數可以很多,連接來了只需要進隊列,不需要啟動線程,事件環線程由事件觸發,沒有多線程阻塞問題。
非同步非阻塞模式也是有利有弊,分析如下圖:
優勢
非同步非阻塞模式啟動的線程很少,基本上一個 CPU core 上只需啟一個事件環處理線程,它使用的線程資源就很少,上下文切換 (Context Switch) 開銷也少。非阻塞模式可以接受的連接數大大增加,可以簡單理解為請求來了只需要進隊列,這個隊列的容量可以設得很大,只要不超時,隊列中的請求都會被依次處理。
不足
非同步模式讓編程模型變得複雜。一方面 Zuul2 本身的代碼要比 Zuul1 複雜很多,Zuul1 的代碼比較容易看懂,Zuul2 的代碼看起來就比較費勁。另一方面非同步模型沒有一個明確清晰的請求 ->處理 ->響應執行流程 (call flow),它的流程是通過事件觸發的,請求處理的流程隨時可能被切換斷開,內部實現要通過一些關聯 id 機制才能把整個執行流再串聯起來,這就給開發調試運維引入了很多複雜性,比如你在 IDE 裡頭調試非同步請求流就非常困難。另外 ThreadLocal 機制在這種非同步模式下就不能簡單工作,因為只有一個事件環線程,不是每個請求一個線程,也就沒有線程局部的概念,所以對於 CAT 這種依賴於 ThreadLocal 才能工作的監控工具,調用鏈埋點就不好搞 (實際可以工作但需要進行特殊處理)。
總體上,非同步非阻塞模式比較適用於 IO 密集型 (IO bound) 場景,這種場景下系統大部分時間在處理 IO,CPU 計算比較輕,少量事件環線程就能處理。
Zuul1 和 Zuul2 的性能比對
Netflix 本身對網關使用非同步非阻塞模式這件事情是非常謹慎的,它們進行了嚴格的性能測試,下面是 Netflix 給出的一些性能數據,來自 Zuul2 網關核心研發成員 Arthur Gonigberg 的 ppt(Zuul"s Journey to Non-Blocking)[附錄 3]:
Netflix 給出了一個比較模糊的數據,大致 Zuul2 的性能比 Zuul1 好 20% 左右,這裡的性能主要指每節點每秒處理的請求數。為什麼說模糊呢?因為這個數據受實際測試環境,流量場景模式等眾多因素影響,你很難復現這個測試數據。即便這個 20% 的性能提升是確實的,其實這個性能提升也並不大,和非同步引入的複雜性相比,這 20% 的提升是否值得是個問題。Netflix 本身在其博文 [附錄 4] 和 ppt[附錄 3] 中也是有點含糊其詞,甚至自身都有一些疑問的。
While we did not see a significant efficiency benefit in migrating to async and non-blocking, we did achieve the goals of connection scaling.
比較明確的是,Zuul2 在連接數方面表現要好於 Zuul1,也就是說 Zuul2 能接受更多的連接數。
Zuul 2.0 架構和額外特性
上圖是 Zuul2 的架構,和 Zuul1 沒有本質區別,兩點變化:
前端用 Netty Server 代替 Servlet,目的是支持前端非同步。後端用 Netty Client 代替 Http Client,目的是支持後端非同步。
過濾器換了一下名字,用 Inbound Filters 代替 Pre-routing Filters,用 Endpoint Filter 代替 Routing Filter,用 Outbound Filters 代替 Post-routing Filters。
上圖是 Zuul2 的一些功能亮點,我個人認為除了對 HTTP/2 的支持算是一個亮點,其它都是在安全、彈性和運維層面的一些優化,不能算新功能。其中像 Request Passport,Status Categories,Request Attempts 這些所謂的新功能,其實是為了減輕非同步帶來的複雜性,方便開發人員調試非同步請求而專門開發的。
建議
基於上述分析,我的建議是在生產環境中繼續使用 Zuul1,原因如下:
Zuul1 同步編程模型簡單,門檻低,開發運維方便,容易調試定位問題。Zuul2 門檻高,調試不方便。
Zuul1 監控埋點容易,比如和調用鏈監控工具 CAT 集成,如果你用 Zuul2 的話,CAT 不好埋點是個問題。
Zuul1 已經開源超過 6 年,穩定成熟,坑已經被踩平。Zuul2 剛開源很新,實際落地案例不多,難說有 bug 需要踩坑。
大部分公司達不到 Netflix 那個量級,Netflix 是要應對每日千億級流量,它們才挖空心思搞非同步,一般公司億級可能都不到,Zuul1 綽綽有餘。
Zuul1 可以集成 Hystrix 熔斷組件,可以部分解決後台服務慢阻塞網關線程的問題。
Zuul1 可以使用 Servlet 3.0 規範支持的 AsyncServlet 進行優化,可以實現前端非同步,支持更多的連接數,達到和 Zuul2 一樣的效果,但是不用引入太多非同步複雜性。在《微服務架構實戰 160 講》視頻課程中,有我關於講解 Zuul1 如何使用 AsyncServlet 優化連接數的內容,戳此了解
對於 Zuul2,我的建議是持謹慎觀望的態度,可以在測試環境小規模實驗驗證,但是暫不上到生產環境。
結論
同步非同步各有利弊,同步多線程編程模型簡單,但會有線程開銷和阻塞問題,非同步非阻塞模式線程少並發高,但是編程模型變得複雜。
架構師做技術選型需要嚴謹務實,具備批判性思維 (Critical Thinking),即使是對於一線大公司推出的開源產品,也要批判性看待,不可盲目追新。
個人建議生產環境繼續使用 Zuul1,同步阻塞模式的一些不足,可以使用熔斷組件 Hystrix 和 AsyncServlet 等技術進行優化。
作者說
我和極客時間合作的課程《微服務架構實戰 160 講》,第三模塊《微服務網關 Zuul 架構和實踐》已經上線,最新的課程模塊會著重對 Zuul 網關生產部署、生產事件等內容進行詳細的講解。希望通過課程的 160 講、8 大核心模塊,幫你打通架構師的進階之路。
※一篇文章即可了解「你的公司到底需不需要微服務」
※MySQL現已正式支持文檔存儲
TAG:InfoQ |