當您選擇將應用程式構建成為一組微服務時,您需要決定應用程式客戶端將如何與微服務進行交互。單體應用程式只有一組端點(endpoint),通常使用複製(replicated)結合負載均衡來分配流量。然而,在微服務架構中,每個微服務都暴露一組通常比較細顆粒的端點。在本文中,我們將研究如何改進客戶端通信,並... ...
鏈接:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese
譯者:Oopsguy
本書的七個章節是關於設計、構建和部署微服務。第一章介紹了微服務架構模式。它闡述使用微服務的優點與缺點,以及儘管如此,微服務通常是複雜應用的理想選擇。該系列的第二篇文章將探討使用 API 網關構建微服務。
當您選擇將應用程式構建成為一組微服務時,您需要決定應用程式客戶端將如何與微服務進行交互。單體應用程式只有一組端點(endpoint),通常使用複製(replicated)結合負載均衡來分配流量。
然而,在微服務架構中,每個微服務都暴露一組通常比較細顆粒的端點。在本文中,我們將研究如何改進客戶端通信,並提出一個使用 API 網關的方案。
2.1、簡介
我們假設您正在為一個購物應用開發一個本地(native)移動客戶端。您可能需要實現一個產品詳細信息頁面,用於展示給定商品的信息。
例如,圖 2-1 展示了在 Amazon 的 Android 移動應用中的滾動產品信息時所看的內容。
即使這是一個智能手機應用,產品詳細信息頁面展示了很多信息。例如,不僅有基本的產品信息,如名稱、描述和價格,頁面還展示了:
- 購物車中的物品數量
- 訂單歷史
- 客戶評論
- 低庫存警告
- 配送選項
- 各種推薦,包括了買了此產品的客戶經常買的其他產品
- 選擇性購買選項
在使用單體應用架構的情況下,移動客戶端通過對應用程式進行單個 REST 調用來檢索此數據,例如:
GET api.company.com/productdetails/productId
負載均衡器將請求路由到幾個相同應用程式實例中的其中一個。之後,應用程式查詢各個資料庫表並返迴響應給客戶端。相比之下,當使用微服務架構時,產品詳細頁面上展示的數據來自多個微服務。以下是一些可能擁有特定商品頁面展示的數據的微服務:
- 訂單服務 - 訂單歷史
- 目錄(catalog)服務 - 基本的產品信息,如產品名稱、圖片和價格
- 評價服務 - 客戶評價
- 庫存服務 - 低庫存警告
- 配送服務 - 配送選項、期限和費用,由配送方的 API 單獨提供
- 推薦服務 - 推薦類目
我們需要決定移動客戶端如何訪問這些服務。讓我們來看看有哪些方式。
2.2、客戶端與微服務直接通信
理論上,客戶端可以直接向每個微服務發送請求。每個微服務都有一個公開的端點:
https://serviceName.api.company.name
該 URL 將映射到用於跨可用實例分發請求的微服務負載均衡器,要檢索特定的產品頁面信息,移動客戶端將向上述的每個微服務發送請求。
不幸的是,這種方式存在著挑戰與限制。第一個問題是客戶端的需求與每個微服務暴露的細粒度的 API 不匹配。此示例中,客戶端需要進行七次單獨請求。如果在更加複雜的應用中,它可能需要做更多的工作。例如,Amazon 展示了在產品頁面渲染中如何牽涉到數百個微服務。雖然客戶端可以通過 LAN 發送許多請求,但在公共互聯網下效率低下,在移動網路必然是不切實際。
客戶端直接調用微服務的另一個問題是有些可能使用了不是 web 友好的協議。一個服務可能使用 Thrift 二進位 RPC,而另一個則可能使用 AMQP 消息協議。這兩個協議無論是對於瀏覽器還是防火牆都是不友好的,最好是在內部使用。應用程式應該在防火牆之外使用 HTTP 或者 WebSocket 之類的協議。
這種方法的另一個缺點是它難以重構微服務。隨著時間推移,我們可能會想改變系統劃分服務。例如,我們可能會合併兩個服務或者將服務拆分為兩個或者多個。然而,如果客戶端直接與服務進行通信,實施這類的重構將變得非常困難。
由於存在這些問題,很少有客戶端直接與微服務進行通信。
2.3、使用 API 網關
通常更好的方法是使用 API 網關。一個 API 網關是一個伺服器,是系統的單入口點。它類似於面向對象設計模式中的門面(Facade)模式。API 網關封裝了內部系統架構,並針對每個客戶端提供一個定製的 API。它還可用於認證、監控、負載均衡、緩存和靜態響應處理。
圖 2-3 展示了 API 通常如何整合架構
API 網關負責請求路由、組合和協議轉換。所有的客戶端請求首先通過 API 網關,之後請求被路由到適當的服務。API 網關通常會通過調用多個微服務和聚合結果來處理一個請求。它可以在 Web 協議(如 HTTP 和 WebSocket)和用於內部的非 Web 友好協議之間進行轉換。
API 還可以為每個客戶端提供一個定製的 API。它通常會為移動客戶端暴露一個粗粒度的 API。例如,考慮一下產品詳細信息場景。API 網關可以提供一個端點 /productdetails?productid=xxx
,如圖 2-3 所示,一個使用了 API 網關的微服務。允許移動客戶端通過一個單獨的請求來檢索所有產品詳細信息。API 網關通過調用各種服務(產品信息、推薦、評價等)並組合結果。
API 網關的一個很好的案例是 Netflix API 網關。Netflix 流媒體服務可用於數百種不同類型的設備,包括電視機、機頂盒、智能手機、游戲機和平板電腦等。起初,Netflix 嘗試為他們的流媒體服務提供一個通用的 API。但是,他們發現由於設備種類繁多,並且他們各自有著不同需求,所以並不是能很好地運作。如今,他們使用了 API 網關,通過運行特定設備適配代碼來為每個設備提供一個定製 API。
2.4、API 網關的優點和缺點
正如您所料,使用 API 網關同樣存在好處與壞處。使用 API 網關的主要好處是它封裝了應用程式的內部結構。客戶端只需要與網關通信,而不必調用特定的服務。API 網關為每種類型的客戶端提供了特定的 API,減少了客戶端與應用程式之間的往返次數。它還簡化了客戶端代碼。
API 網關也存在一些缺點,它是另一個高度可用的組件,需要開發、部署和管理。還有另一個風險是 API 網關可能會成為開發瓶頸。開發人員必須更新 API 網關以暴露每個微服務的端點。
重要的是更新 API 網關的過程應儘可能地輕一些。否則,開發人員將被迫排隊等待網關更新。儘管 API 網關存在這些缺點,但對於大多數的真實應用來說,使用 API 是合理的。
2.5、實施 API 網關
我們已經瞭解了使用 API 網關的動機與權衡。接下來讓我們看看您需要考慮的各種設計問題。
2.5.1、性能與可擴展性
只有少數公司能達到 Netflix 的運營規模,每天需要處理數十億的請求。然而,對於大多數應用來說,API 網關的性能和可擴展性是相當重要的。因此,在一個支持非同步、非阻塞 I/O 平臺上構建 API 網關是有必要的。可以使用不同的技術來實現一個可擴展的 API 網關。在 JVM 上,您可以使用基於 NIO 的框架,如Netty、Vertx、Spring Reactor 或者 JBoss Undertow。一個流行的非 JVM 選擇是使用 Node.js,它是一個建立在 Chrome 的 JavaScript 引擎的平臺。另一個選擇是使用 NGINX Plus。
NGINX Plus 提供了一個成熟、可擴展和高性能的 Web 伺服器和反向代理,它易於部署、配置和編程。NGINX Plus 可以管理身份驗證、訪問控制、負載均衡請求、緩存響應,並且提供了應用程式健康檢查和監控功能。
2.5.2、使用響應式編程模型
API 網關通過簡單地把他們(請求)路由到適當的後端服務來處理一些請求。它通過調用多個後端服務並聚合結果來處理其他請求。對於某些請求,如產品詳細信息請求,對後端服務請求而言是彼此獨立的。為了縮短響應時間到最小,API 網關應該併發執行獨立請求。
然而,有時候,請求是相互依賴的。首先,API 網關可能需要在將請求路由到後端服務之前,通過調用驗證服務來驗證請求。同樣,為了從客戶的願望清單中獲取關於產品的信息,API 網關首先必須檢索包含該信息的客戶資料,然後檢索每個產品的信息。API 組合的另一個有趣的案例是 Netflix 視頻網格。
使用傳統的非同步回調方式來編寫 API 組合代碼會很快使你陷入回調地獄。代碼將會變得雜亂,難以理解,並且容易出錯。一個更好的方式是使用響應式方法以聲明式編寫 API 網關代碼。響應式抽象的例子包括 Scala 的 Future、Java 8 中的 CompletableFuture 和 JavaScript 中的 Promise。還有 Reactive Extensions(也稱為 Rx 或 ReactiveX),最初由 Microsoft 為 .NET 平臺開發。Netflix 為 JVM 創建了 RxJava,專門應用於其 API 網關。還有用於 JavaScript 的 RxJS,它可以在瀏覽器和 Node.js 中運行。使用響應式方式可讓您能夠編寫出簡單而高效的 API 網關代碼。
2.5.3、服務調用
一個基於微服務的應用程式是一個分散式系統,必須使用一個進程間(inter-process)通信機制。有兩種進程間通信方案。一是使用基於消息的非同步機制。某些實現採用了消息代理,如 JMS 和 AMQP。其他採用無代理的方式直接與服務通信,如 Zeromq。
另一種類型的進程間通信採用了同步機制,如 HTTP 和 Thrift。系統通常會同時使用非同步和同步方式。甚至可以為每種方式應用多個實現。因此,API 網關需要支持各種通信機制。
2.5.4、服務發現
API 網關需要知道與其通信的每個微服務的位置(IP 地址和埠)。傳統應用程式中,您可以將這些位置硬編碼,但在現代基於雲的微服務應用程式中,找到所需的位置不是一個簡單的問題。
基礎設施服務(比如消息代理)通常都有一個可以通過系統環境變數來指定的靜態位置。但是,要確定應用程式服務的位置並不是那麼容易。
應用服務可以動態分配位置。此外,由於自動擴展與升級,一個服務的整組實例可以動態變更。因此,API 網關與系統中的任何其他服務客戶端一樣,需要使用系統的服務發現機制:服務端發現或客戶端發現。 第4章中更詳細地描述了服務發現。現在需要註意的是,如果系統使用客戶端發現,API 網關必須能夠查詢服務註冊表,該註冊表是所有微服務實例及其位置的資料庫。
2.5.5、處理部分故障
實施 API 網關時必須解決的另一個問題是部分故障問題。當一個服務調用另一個響應緩慢或者不可用的服務時,所有分散式系統都會出現此問題。API 網關不應該無限期地等待下游服務。但是,如何處理故障問題取決於特定的方案和哪些服務發生故障。例如,如果推薦服務在獲取產品詳細信息時沒有響應,API 網關應將其餘的產品詳細信息返回給客戶端,因為它們對用戶仍然有用。建議可以是空的,也可以用其他代替,例如硬編碼的十強名單。然而,如果產品信息服務沒有響應,那麼 API 網關應該向客戶端返回錯誤。
如果可以,API 網關還可以返回緩存數據。例如,由於產品價格變化不大,如果價格服務不可用,API 網關可以返回被緩存的價格數據。數據可以由 API 網關緩存或存儲在外部緩存中,如 Redis 或者 Memcached。API 網關通過返回預設數據或緩存數據,確保了系統發生故障時最小程度上影響到用戶體驗。
Netflix Hystrix 是用於編寫調用遠程服務代碼的一個非常有用的庫。Hystrix 可以使超出指定閾值的調用超時。它實現了斷路器模式,防止客戶端不必要地等待無響應的服務。如果服務的錯誤率超過指定閾值,Hystrix 將會跳閘,所有請求將在指定的時間內立即失敗。Hystrix 允許您在請求失敗時定義回退操作,例如從緩存讀取或返回預設值。如果您正在使用 JVM,那麼您一定要考慮使用 Hystrix。如果您是在非 JVM 環境中運行,則應使用同等作用的庫。
2.6、總結
對於大多數基於微服務的應用程式來說,實現一個 API 網關是很有意義的,API 網關充當著系統的單入口點,並且負責請求路由,組合和協議轉換。它為每個應用程式客戶端提供了一個自定義 API。API 網關還可以通過返回緩存或預設數據來掩蓋後端服務故障。在下一章中,我們將介紹服務間的通信。
微服務實戰:NGINX Plus 作為 API 網關
by Floyd Smith
本章討論了 API 網關如何作為系統的單入口點。它可以處理諸如負載均衡、緩存、監控和協議轉換等其他功能 —— 當 NGINX 充當一個反向代理伺服器時,可以作為系統的單入口點,並且支持所有提到的一個 API 網關具有的附加功能。因此使用 NGINX 作為 API 網關的主機可以很好地工作。
將 NGINX 作為 API 網關並不是本書最開始的想法。NGINX Plus 是用於管理和保護基於 HTTP 的 API 流量的領先平臺。您可以實現自己的 API 網關或使用現有的 API 管理平臺,其中許多使用了 NGINX。
使用 NGINX Plus 作為 API 網關的理由包括:
- 訪問管理 - 上至典型的 Web 應用級別,下至每個個體微服務級別,您都可以使用各種訪問控制列表(ACL)方法,並且可以輕鬆實現 SSL/TLS。
- 可管理性與彈性 - 您可以使用 NGINX 的動態重新配置 API、Lua 模塊、Perl 來更新基於 NGINX Plus 的 API 伺服器,也可以通過 Chef、Puppet、ZooKeeper 或 DNS 來改變。
- 與第三方工具集成 - NGINX Plus 已經可以與某些先進的工具集成在一起,如 3scale,Kong 和 MuleSoft 集成平臺(僅列舉在 NGINX 網站上提及的工具)。
NGINX Plus 被廣泛用作 NGINX 微服務參考架構中的 API 網關。利用在這裡收集的文章以及 MRA (微服務參考架構)來瞭解如何在您自己的應用程式中實現這一點。