1. 程式人生 > >API閘道器效能比較:NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd

API閘道器效能比較:NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd

前幾天拜讀了 OpsGenie 公司(一家致力於 Dev & Ops 的公司)的資深工程師 Turgay Çelik 博士寫的一篇文章(連結在文末),文中介紹了他們最初也是採用 Nginx 作為單體應用的閘道器,後來接觸到微服務架構後開始逐漸採用了其他元件。

我對於所做的工作或者感興趣的技術,喜歡刨根問底,所以當讀一篇文章時發現沒有看到我想要看到的設計思想,我就會四處蒐集資料,此外這篇文章涉及了我正在搗鼓的 Spring Cloud,所以我就決定寫一篇文章,爭取能從設計思路上解釋為什麼會有這樣的效能差異。

技術介紹

文中針對 Nginx、ZUUL、Spring Cloud、Linkerd 等技術進行了對比(其實還有 Envoy 和 UnderTow 也是屬於可選的 API 閘道器,本文不予涉及),那我就分別進行介紹,當然,首先得介紹 API 閘道器。

API 閘道器

API 網關出現的原因是微服務架構的出現,不同的微服務一般會有不同的網路地址,而外部客戶端可能需要呼叫多個服務的接口才能完成一個業務需求,如果讓客戶端直接與各個微服務通訊,會有以下的問題:

  1. 客戶端會多次請求不同的微服務,增加了客戶端的複雜性。
  2. 存在跨域請求,在一定場景下處理相對複雜。
  3. 認證複雜,每個服務都需要獨立認證。
  4. 難以重構,隨著專案的迭代,可能需要重新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分成多個。如果客戶端直接與微服務通訊,那麼重構將會很難實施。
  5. 某些微服務可能使用了防火牆 / 瀏覽器不友好的協議,直接訪問會有一定的困難。

以上這些問題可以藉助 API 閘道器解決。API 閘道器是介於客戶端和伺服器端之間的中間層,所有的外部請求都會先經過 API 閘道器這一層。也就是說,API 的實現方面更多的考慮業務邏輯,而安全、效能、監控可以交由 API 閘道器來做,這樣既提高業務靈活性又不缺安全性,典型的架構圖如圖所示:

使用 API 閘道器後的優點如下:

  • 易於監控。可以在閘道器收集監控資料並將其推送到外部系統進行分析。
  • 易於認證。可以在閘道器上進行認證,然後再將請求轉發到後端的微服務,而無須在每個微服務中進行認證。
  • 減少了客戶端與各個微服務之間的互動次數。

NGINX 服務

Nginx 由核心和模組組成,核心的設計非常微小和簡潔,完成的工作也非常簡單,僅僅通過查詢配置檔案與客戶端請求進行 URL 匹配,用於啟動不同的模組去完成相應的工作。

下面這張圖反應的是 HTTP 請求的常規處理流程:

Nginx 的模組直接被編譯進 Nginx,因此屬於靜態編譯方式。啟動 Nginx 後,Nginx 的模組被自動載入,不像 Apache,首先將模組編譯為一個 so 檔案,然後在配置檔案中指定是否進行載入。在解析配置檔案時,Nginx 的每個模組都有可能去處理某個請求,但是同一個處理請求只能由一個模組來完成。

Nginx 在啟動後,會有一個 Master 程序和多個 Worker 程序,Master 程序和 Worker 程序之間是通過程序間通訊進行互動的,如圖所示。Worker 工作程序的阻塞點是在像 select()、epoll_wait() 等這樣的 I/O 多路複用函式呼叫處,以等待發生資料可讀 / 寫事件。Nginx 採用了非同步非阻塞的方式來處理請求,也就是說,Nginx 是可以同時處理成千上萬個請求的。一個 Worker 程序可以同時處理的請求數只受限於記憶體大小,而且在架構設計上,不同的 Worker 程序之間處理併發請求時幾乎沒有同步鎖的限制,Worker 程序通常不會進入睡眠狀態,因此,當 Nginx 上的程序數與 CPU 核心數相等時(最好每一個 Worker 程序都繫結特定的 CPU 核心),程序間切換的代價是最小的。

Netflix 的 Zuul

Zuul 是 Netflix 開源的微服務閘道器元件,它可以和 Eureka、Ribbon、Hystrix 等元件配合使用。Zuul 的核心是一系列的過濾器,這些過濾器可以完成以下功能:

  • 身份認證與安全:識別每個資源的驗證要求,並拒絕那些與要求不符的請求。
  • 審查與監控:與邊緣位置追蹤有意義的資料和統計結果,從而帶來精確的生產檢視。
  • 動態路由:動態地將請求路由到不同的後端叢集。
  • 壓力測試:逐漸增加指向叢集的流量,以瞭解效能。
  • 負載分配:為每一種負載型別分配對應容量,並棄用超出限定值的請求。
  • 靜態響應處理:在邊緣位置直接建立部分響應,從而避免其轉發到內部叢集。
  • 多區域彈性:跨越 AWS Region 進行請求路由,旨在實現 ELB(Elastic Load Balancing,彈性負載均衡)使用的多樣化,以及讓系統的邊緣更貼近系統的使用者。

上面提及的這些特性是 Nigix 所沒有的,這是因為 Netflix 公司創造 Zuul 是為了解決雲端的諸多問題(特別是幫助 AWS 解決跨 Region 情況下的這些特性實現),而不僅僅是做一個類似於 Nigix 的反向代理,當然,我們可以僅使用反向代理功能,這裡不多做描述。

Zuul1 是基於 Servlet 框架構建,如圖所示,採用的是阻塞和多執行緒方式,即一個執行緒處理一次連線請求,這種方式在內部延遲嚴重、裝置故障較多情況下會引起存活的連線增多和執行緒增加的情況發生。

Zuul2 的巨大區別是它執行在非同步和無阻塞框架上,每個 CPU 核一個執行緒,處理所有的請求和響應,請求和響應的生命週期是通過事件和回撥來處理的,這種方式減少了執行緒數量,因此開銷較小。又由於資料被儲存在同一個 CPU 裡,可以複用 CPU 級別的快取,前面提及的延遲和重試風暴問題也通過佇列儲存連線數和事件數方式減輕了很多(較執行緒切換來說輕量級很多,自然消耗較小)。這一變化一定會大大提升效能,我們在後面的測試環節看看結果。

我們今天談的是 API 閘道器效能,這一點也涉及到高可用,簡單介紹 Zuul 的高可用特性,高可用是非常關鍵的,因為外部請求到後端微服務的流量都會經過 Zuul,所以在生產環境中一般都需要部署高可用的 Zuul 來避免單點故障。一般我們有兩種部署方案:

1. Zuul 客戶端註冊到 Eureka Server

這種情況是比較簡單的情況,只需要將多個 Zuul 節點註冊到 Eureka Server 上,就可以實現 Zuul 的高可用。事實上,這種情況下的高可用和其他服務做高可用的方案沒有什麼區別。我們來看下面這張圖,當 Zuul 客戶端註冊到 Eureka Server 上時,只需要部署多個 Zuul 節點就可以實現高可用。Zuul 客戶端會自動從 Eureka Server 查詢 Zuul Server 列表,然後使用負載均衡元件(例如 Ribbon)請求 Zuul 叢集。

2. Zuul 客戶端不能註冊到 Eureka Server

假如說我們的客戶端是手機端 APP,那麼不可能通過方案 1 的方式註冊到 Eureka Server 上。這種情況下,我們可以通過額外的負載均衡器來實現 Zuul 的高可用,例如 Nginx、HAProxy、F5 等。

如圖所示,Zuul 客戶端將請求傳送到負載均衡器,負載均衡器將請求轉發到其代理的其中一個 Zuul 節點,這樣就可以實現 Zuul 的高可用。

Spring Cloud

雖然 Spring Cloud 帶有“Cloud”,但是它並不是針對雲端計算的解決方案,而是在 Spring Boot 基礎上構建的,用於快速構建分散式系統的通用模式的工具集。

使用 Spring Cloud 開發的應用程式非常適合在 Docker 或者 PaaS 上部署,所以又叫雲原生應用。雲原生可以簡單理解為面向雲環境的軟體架構。

既然是工具集,那麼它一定包含很多工具,我們來看下面這張圖:

這裡由於僅涉及到 API 閘道器的對比,因此我不逐一介紹其他工具了。

Spring Cloud 對 Zuul 進行了整合,但從 Zuul 來看,沒有大變化,但是 Spring Cloud 整個框架經過了元件的整合,提供的功能遠多於 Netflix Zuul,可能對比時會出現差異。

Service Mesh 之 Linkerd

我想 Turgay Celik 博士把 Linkerd 作為對比物件之一,可能是因為 Linkerd 為雲原生應用提供彈性的 Service Mesh,而 Service Mesh 能夠提供輕量級高效能網路代理,並且也提供微服務框架支撐。

從介紹來看,linkerd 是我們面向微服務的開源 RPC 代理,它直接立足於 Finagle(Twitter 的內部核心庫,負責管理不同服務間之通訊流程。事實上,Twitter 公司的每一項線上服務都立足於 Finagle 構建而成,而且其支援著每秒發生的成百上千萬條 RPC 呼叫)構建而成,設計目標在於幫助使用者簡化微服務架構下的運維,它是專用於處理時間敏感的服務到服務的通訊基礎設施層。

和 Spring Cloud 類似,Linkerd 也提供了負載均衡、熔斷機器、服務發現、動態請求路由、重試和離線、TLS、HTTP 閘道器整合、透明代理、gRPC、分散式跟蹤、運維等諸多功能,功能是相當全了,為微服務框架的技術選型又增加了一個。由於沒有接觸過 Linkerd,所以暫時無法從架構層面進行分析,後續會補充這方面的內容,自己來做一次技術選型。

效能測試結果

Turgay Çelik 博士的那篇文章裡使用了 Apache 的 HTTP 伺服器效能評估工具 AB 作為測試工具。注意,由於他是基於亞馬遜(AWS)公有云的進行的測試,可能和你實際物理機上的測試結果有出入。

實驗中啟動了客戶端和服務端兩臺機器,分別安裝多個待測試服務,客戶端通過幾種方式分別訪問,嘗試獲取資源。測試方案如下圖所示:

Turgay Çelik 博士的這次測試選擇了三個環境,分別是:

  1. 單 CPU 核,1GB 記憶體:用於比較 Nginx 反向代理和 Zuul(去除第一次執行後的平均結果);
  2. 雙 CPU 核,8GB 記憶體:用於比較 Nginx 反向代理和 Zuul(去除第一次執行後的平均結果);
  3. 8 個核 CPU,32GB 記憶體:用於比較 Nginx 反向代理、Zuul(去除第一次執行後的平均結果)、Spring Cloud Zuul、Linkerd。

測試過程均採用 200 個並行執行緒傳送總共 1 萬次請求,命令模板如下所示:

ab -n 10000 -c 200 HTTP://<server-address>/<path to resource>

注意:由於 Turgay Çelik 博士的測試過程中是基於 Zuul 1 進行的測試,所以效能上較差,不能真實反映當前 Zuul 版本的效能狀況,後續文章我會自己做實驗併發布結果。

從上面的結果來看,單核環境下,Zuul 的效能最差(950.57 次 /s),直接訪問方式效能最好(6519.68 次 /s),採用 Nginx 反向代理方式較直接訪問方式損失 26% 的效能(4888.24 次 /s)。在雙核環境下,Nginx 的效能較 Zuul 效能強接近 3 倍(分別是 6187.14 次 /s 和 2099.93 次 /s)。在較強的測試環境下(8 核),直接訪問、Nginx、Zuul 差距不大,但是 Spring Cloud Zuul 可能由於內部整體消耗,導致每秒的請求數只有 873.14。

最終結論

從產品思維來看,API 閘道器負責服務請求路由、組合及協議轉換。客戶端的所有請求都首先經過 API 閘道器,然後由它將請求路由到合適的微服務。API 閘道器經常會通過呼叫多個微服務併合並結果來處理一個請求,它可以在 Web 協議(如 HTTP 與 WebSocket)與內部使用的非 Web 友好協議之間轉換,所以說作用還是很大的,因此技術方案選型對於整個系統來說也有一定重要性。

從我所理解的這四款元件的設計原理來看,Zuul1 的設計模式和 Nigix 較像,每次 I/O 操作都是從工作執行緒中選擇一個執行,請求執行緒被阻塞直到工作執行緒完成,但是差別是 Nginx 用 C++ 實現,Zuul 用 Java 實現,而 JVM 本身有第一次載入較慢的情況。Zuul2 的效能肯定會較 Zuul1 有較大的提升,此外,Zuul 的第一次測試效能較差,但是從第二次開始就好了很多,可能是由於 JIT(Just In Time)優化造成的吧。而對於 Linkerd,它本身是對於資源比較敏感的一種閘道器設計,所以在通用環境下拿它和其他閘道器實現相比較,可能會出現不準確的結果。

作者介紹

周明耀,畢業於浙江大學,工學碩士。13 年軟體開發領域工作經驗,10 年技術管理經驗,4 年分散式軟體開發經驗,提交發明專利 17 項。著有《大話 Java 效能優化》、《深入理解 JVM&G1 GC》、《技術領導力 程式設計師如何才能帶團隊》。微訊號 michael_tec,微信公眾號“麥克叔叔每晚 10 點說”。

原文連結如下:

英文原文連結如下:

https://engineering.opsgenie.com/comparing-api-gateway-performances-nginx-vs-zuul-vs-spring-cloud-gateway-vs-linkerd-b2cc59c65369