Zuul網(wǎng)關(guān)2.0vs1.0

在今年5月中,Netflix終于開源了它的支持異步調(diào)用模式的Zuul網(wǎng)關(guān)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云網(wǎng)關(guān)集群,流量經(jīng)過Zuul2集群被路由到后端超過100+的微服務(wù),且每秒鐘經(jīng)過Zuul2集群的請求超過100萬。

Zuul2看起來很強(qiáng)大,支持異步高并發(fā)(Zuul1僅支持同步)特性看起來很亮眼,那么我們是否就應(yīng)該拋棄Zuul1,開始擁抱Zuul2呢?作為架構(gòu)師,我們不能盲目追時髦,技術(shù)的選擇必須基于實踐和理性的分析,基于我之前對Zuul1的一線落地實戰(zhàn)經(jīng)驗,也基于我近期對Zuul2的一些調(diào)研,我會在本文中對Zuul1和Zuul2做一個比較客觀的編程模型和優(yōu)劣分析,同時給出我的個人建議。

Zuul 1.0編程模型和優(yōu)劣

image

Zuul1設(shè)計比較簡單,代碼不多也比較容易讀懂,它本質(zhì)上就是一個同步Servlet,采用多線程阻塞模型,如上圖所示[圖片來自附錄4]。

同步Servlet使用thread per connection方式處理請求。簡單講,每來一個請求,Servlet容器要為該請求分配一個線程專門負(fù)責(zé)處理這個請求,直到響應(yīng)返回客戶端這個線程才會被釋放返回容器線程池。如果后臺服務(wù)調(diào)用比較耗時,那么這個線程就會被阻塞,阻塞期間線程資源被占用,不能干其它事情。我們知道Servlet容器線程池的大小是有限制的,當(dāng)前端請求量大,而后臺慢服務(wù)比較多時,很容易耗盡容器線程池內(nèi)的線程,造成容器無法接受新的請求,Netflix為此還專門研發(fā)了Hystrix[附錄2]熔斷組件來解決慢服務(wù)耗盡資源問題。

注意在上圖Netflix給出的場景中,它的后臺服務(wù)調(diào)用也是啟動另外一個IO線程來處理的,但是本質(zhì)上還是阻塞模式,后臺IO線程在處理的時候,前臺容器線程仍然是阻塞的。

同步阻塞模式有利有弊,分析如下圖:

image

優(yōu)勢

同步阻塞模式的編程模型比較簡單,整個請求->處理->響應(yīng)的流程(call flow)都是在一個線程中處理的,這樣開發(fā)調(diào)試比較方便易于理解,比如出了問題跟蹤調(diào)試比較方便。另外,線程局部變量(ThreadLocal)機(jī)制在同步多線程模式下可以工作,有些監(jiān)控產(chǎn)品,例如CAT調(diào)用鏈依賴于ThreadLocal,在同步多線程模式下,CAT埋點比較方便,調(diào)用鏈關(guān)系的展示也比較直觀。

不足

我們知道線程本身需要消耗CPU和內(nèi)存資源,且多線程之間切換是有開銷的(所謂的上下文切換Context Switch開銷),線程越多,這種上下文切換的開銷就越大,同步阻塞模式一般會啟動很多的線程,必然引入線程切換開銷。另外,同步阻塞模式下,容器線程池的數(shù)量一般是固定的,造成對連接數(shù)有一定限制,當(dāng)后臺服務(wù)慢,容器線程池易被耗盡,一旦耗盡容器會拒絕新的請求,這個時候容器線程其實并不忙,只是被后臺服務(wù)調(diào)用IO阻塞,但是干不了其它事情。

總體上,同步阻塞模式比較適用于計算密集型(CPU bound)應(yīng)用場景。對于IO密集型場景(IO bound),同步阻塞模式會白白消耗很多線程資源,它們都在等待IO的阻塞狀態(tài),沒有做實質(zhì)性工作。

Zuul 2.0編程模型和優(yōu)劣

image

Zuul2的設(shè)計相對比較復(fù)雜,代碼也不太容易讀懂,它采用了Netty實現(xiàn)異步非阻塞編程模型,如上圖所示[圖片來自附錄4]。

一般異步模式的本質(zhì)都是使用隊列Queue(或稱總線Bus),在上圖中,你可以簡單理解為前端有一個隊列專門負(fù)責(zé)處理用戶請求,后端有個隊列專門負(fù)責(zé)處理后臺服務(wù)調(diào)用,中間有個事件環(huán)線程(Event Loop Thread),它同時監(jiān)聽前后兩個隊列上的事件,有事件就觸發(fā)回調(diào)函數(shù)處理事件。這種模式下需要的線程比較少,基本上每個CPU核上只需要一個事件環(huán)處理線程,前端的連接數(shù)可以很多,連接來了只需要進(jìn)隊列,不需要啟動線程,事件環(huán)線程由事件觸發(fā),沒有多線程阻塞問題。

異步非阻塞模式也是有利有弊,分析如下圖:

image

優(yōu)勢

異步非阻塞模式啟動的線程很少,基本上一個CPU core上只需啟一個事件環(huán)處理線程,它使用的線程資源就很少,上下文切換(Context Switch)開銷也少。非阻塞模式可以接受的連接數(shù)大大增加,可以簡單理解為請求來了只需要進(jìn)隊列,這個隊列的容量可以設(shè)得很大,只要不超時,隊列中的請求都會被依次處理。

不足

異步模式讓編程模型變得復(fù)雜。一方面Zuul2本身的代碼要比Zuul1復(fù)雜很多,Zuul1的代碼比較容易看懂,Zuul2的代碼看起來就比較費勁。另一方面異步模型沒有一個明確清晰的請求->處理->響應(yīng)執(zhí)行流程(call flow),它的流程是通過事件觸發(fā)的,請求處理的流程隨時可能被切換斷開,內(nèi)部實現(xiàn)要通過一些關(guān)聯(lián)id機(jī)制才能把整個執(zhí)行流再串聯(lián)起來,這就給開發(fā)調(diào)試運維引入了很多復(fù)雜性,比如你在IDE里頭調(diào)試異步請求流就非常困難。另外ThreadLocal機(jī)制在這種異步模式下就不能簡單工作,因為只有一個事件環(huán)線程,不是每個請求一個線程,也就沒有線程局部的概念,所以對于CAT這種依賴于ThreadLocal才能工作的監(jiān)控工具,調(diào)用鏈埋點就不好搞(實際可以工作但需要進(jìn)行特殊處理)。

總體上,異步非阻塞模式比較適用于IO密集型(IO bound)場景,這種場景下系統(tǒng)大部分時間在處理IO,CPU計算比較輕,少量事件環(huán)線程就能處理。

Zuul1和Zuul2的性能比對

Netflix本身對網(wǎng)關(guān)使用異步非阻塞模式這件事情是非常謹(jǐn)慎的,它們進(jìn)行了嚴(yán)格的性能測試,下面是Netflix給出的一些性能數(shù)據(jù),來自Zuul2網(wǎng)關(guān)核心研發(fā)成員Arthur Gonigberg的ppt(Zuul's Journey to Non-Blocking)[附錄3]:

image

Netflix給出了一個比較模糊的數(shù)據(jù),大致Zuul2的性能比Zuul1好20%左右,這里的性能主要指每節(jié)點每秒處理的請求數(shù)。為什么說模糊呢?因為這個數(shù)據(jù)受實際測試環(huán)境,流量場景模式等眾多因素影響,你很難復(fù)現(xiàn)這個測試數(shù)據(jù)。即便這個20%的性能提升是確實的,其實這個性能提升也并不大,和異步引入的復(fù)雜性相比,這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在連接數(shù)方面表現(xiàn)要好于Zuul1,也就是說Zuul2能接受更多的連接數(shù)。

Zuul 2.0架構(gòu)和額外特性

image

上圖是Zuul2的架構(gòu),和Zuul1沒有本質(zhì)區(qū)別,兩點變化:

  1. 前端用Netty Server代替Servlet,目的是支持前端異步。后端用Netty Client代替Http Client,目的是支持后端異步。

  2. 過濾器換了一下名字,用Inbound Filters代替Pre-routing Filters,用Endpoint Filter代替Routing Filter,用Outbound Filters代替Post-routing Filters。

image

上圖是Zuul2的一些功能亮點,我個人認(rèn)為除了對HTTP/2的支持算是一個亮點,其它都是在安全、彈性和運維層面的一些優(yōu)化,不能算新功能。其中像Request Passport,Status Categories,Request Attempts這些所謂的新功能,其實是為了減輕異步帶來的復(fù)雜性,方便開發(fā)人員調(diào)試異步請求而專門開發(fā)的。

建議

基于上述分析,我對大家的建議是在生產(chǎn)環(huán)境中繼續(xù)使用Zuul1,原因如下:

  1. Zuul1同步編程模型簡單,門檻低,開發(fā)運維方便,容易調(diào)試定位問題。Zuul2門檻高,調(diào)試不方便。

  2. Zuul1監(jiān)控埋點容易,比如和調(diào)用鏈監(jiān)控工具CAT集成,如果你用Zuul2的話,CAT不好埋點是個問題。

  3. Zuul1已經(jīng)開源超過6年,穩(wěn)定成熟,坑已經(jīng)被踩平。Zuul2剛開源很新,實際落地案例不多,難說有bug需要踩坑。

  4. 大部分公司達(dá)不到Netflix那個量級,Netflix是要應(yīng)對每日千億級流量,它們才挖空心思搞異步,一般公司億級可能都不到,Zuul1綽綽有余。

  5. Zuul1可以集成Hystrix熔斷組件,可以部分解決后臺服務(wù)慢阻塞網(wǎng)關(guān)線程的問題。

  6. Zuul1可以使用Servlet 3.0規(guī)范支持的AsyncServlet進(jìn)行優(yōu)化,可以實現(xiàn)前端異步,支持更多的連接數(shù),達(dá)到和Zuul2一樣的效果,但是不用引入太多異步復(fù)雜性。波波和極客時間合作的課程《微服務(wù)架構(gòu)和實踐160講》,7月份馬上上線第三模塊《微服務(wù)網(wǎng)關(guān)Zuul架構(gòu)和實踐》,其中會講解Zuul1如何使用AsyncServlet優(yōu)化連接數(shù),歡迎大家關(guān)注。

對于Zuul2,我的建議是持謹(jǐn)慎觀望的態(tài)度,可以在測試環(huán)境小規(guī)模實驗驗證,但是暫不上到生產(chǎn)環(huán)境。

結(jié)論

  1. 同步異步各有利弊,同步多線程編程模型簡單,但會有線程開銷和阻塞問題,異步非阻塞模式線程少并發(fā)高,但是編程模型變得復(fù)雜。

  2. 架構(gòu)師做技術(shù)選型需要嚴(yán)謹(jǐn)務(wù)實,具備批判性思維(Critical Thinking),即使是對于一線大公司推出的開源產(chǎn)品,也要批判性看待,不可盲目追新。

  3. 個人建議生產(chǎn)環(huán)境繼續(xù)使用Zuul1,同步阻塞模式的一些不足,可以使用熔斷組件Hystrix和AsyncServlet等技術(shù)進(jìn)行優(yōu)化。波波和極客時間合作的課程《微服務(wù)架構(gòu)和實踐160講》,7月份馬上上線第三模塊《微服務(wù)網(wǎng)關(guān)Zuul架構(gòu)和實踐》,其中會講解對Zuul1的這些優(yōu)化技術(shù),歡迎大家關(guān)注。

附錄

  1. Open Sourcing Zuul 2 https://medium.com/netflix-techblog/open-sourcing-zuul-2-82ea476cb2b3

  2. Hystrix https://github.com/netflix/hystrix

  3. Zuul's Journey to Non-Blocking https://github.com/strangeloop/StrangeLoop2017/blob/master/slides/ArthurGonigberg-ZuulsJourneyToNonBlocking.pdf

  4. Zuul2:Netflix's Journey to Asynchronous,Non-blocking Systems https://medium.com/netflix-techblog/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c

- END -

** 往期推薦:**

  • 死磕Java系列:
  1. 深入分析ThreadLocal

  2. 深入分析synchronized的實現(xiàn)原理

  3. 深入分析volatile的實現(xiàn)原理

  4. Java內(nèi)存模型之happens-before

  5. Java內(nèi)存模型之重排序

  6. Java內(nèi)存模型之分析volatile

  7. Java內(nèi)存模型之總結(jié)

  8. J.U.C之AQS簡介

  9. J.U.C之AQS:CLH同步隊列

  10. J.U.C之AQS同步狀態(tài)的獲取與釋放

  11. J.U.C之AQS阻塞和喚醒線程

  12. J.U.C之重入鎖:ReentrantLock

  13. J.U.C之讀寫鎖:ReentrantReadWriteLock

  14. J.U.C之Condition

  15. J.U.C之并發(fā)工具類:CyclicBarrier

  16. J.U.C之并發(fā)工具類:Semaphore

  17. J.U.C之并發(fā)工具類:CountDownLatch

……

  • Spring系列:
  1. Spring Cloud Zuul中使用Swagger匯總API接口文檔

  2. Spring Cloud Config Server遷移節(jié)點或容器化帶來的問題

  3. Spring Cloud Config對特殊字符加密的處理

  4. Spring Boot使用@Async實現(xiàn)異步調(diào)用:使用Future以及定義超時

  5. Spring Cloud構(gòu)建微服務(wù)架構(gòu):分布式配置中心(加密解密)

  6. Spring Boot快速開發(fā)利器:Spring Boot CLI

……

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,246評論 8 265
  • 進(jìn)惠(簡書作者) 初識減齡 我知道減齡是在2017年11月14日,小楊是我的一個小朋友,20多歲的美麗能干寶媽,和...
    楊進(jìn)惠閱讀 1,972評論 0 4
  • 愚子讀《論語》(四) 生活中,大多數(shù)人或多或少遭受過別人的輕視或者羞辱,心中那種滋味著實不好受。很多人經(jīng)歷過這些后...
    公子小白ahan閱讀 181評論 1 1
  • 說起咖啡館,很多人一瞬間會想到星巴克、Costa,還有日漸聞名的漫咖啡。其實在國外,尤其是歐洲,獨立咖啡店的地位是...
    現(xiàn)實照進(jìn)理想閱讀 1,868評論 0 5

友情鏈接更多精彩內(nèi)容