使用 API 閘道器構建微服務-2
「Chris Richardson 微服務系列」使用 API 閘道器構建微服務
Posted on 2016年5月12日編者的話|本文來自 Nginx 官方部落格,是微服務系列文章的第二篇,本文將探討:微服務架構是如何影響客戶端到服務端的通訊,並提出一種使用 API 閘道器的方法。
轉自http://blog.daocloud.io/microservices-2/
作者介紹:Chris Richardson,是世界著名的軟體大師,經典技術著作《POJOS IN ACTION》一書的作者,也是 cloudfoundry.com 最初的創始人,Chris Richardson 與 Martin Fowler、Sam Newman、Adrian Cockcroft 等並稱為世界十大軟體架構師。
Chris Richardson 微服務系列全 7 篇:
1. 微服務架構的優勢與不足
2. 使用 API 閘道器構建微服務
3. 深入微服務架構的程序間通訊
4. 服務發現的可行方案以及實踐案例
5. 微服務的事件驅動資料管理
6. 選擇微服務部署策略
7. 將單體應用改造為微服務
Chris Richardson 所著所有文章已獨家授權 DaoCloud 翻譯並刊載。
本期內容
微服務系列文章的第一篇介紹了微服務架構模式,討論了使用微服務的優缺點,以及為什麼微服務雖然複雜度高卻是複雜應用程式的理想選擇。
在決定以一組微服務來構建自己的應用時,你需要確定應用客戶端如何與微服務互動。
在單體式程式中,通常只有一組冗餘的或者負載均衡的服務提供點。在微服務架構中,每一個微服務暴露一組細粒度的服務提供點。在本篇文章中,我們來看它如何影響客戶端到服務端通訊,並提出一種使用 API 閘道器的方法。
簡要概述
讓我們想象一下,你要為一個購物應用程式開發一個原生移動客戶端。你很可能需要實現一個產品詳情頁面,展示任何指定商品的資訊。
下圖展示了 Amazon Android 應用在商品詳情頁顯示的內容。
即使只是個智慧手機應用,產品詳情頁面也顯示了大量的資訊。該頁面不僅包含基本的產品資訊(如名稱、描述、價格),而且還顯示瞭如下內容:
- 購物車中的商品數量
- 歷史訂單
- 客戶評論
- 低庫存預警
- 送貨選項
- 各種推薦,包括經常與該商品一起購買的其它商品、購買該商品的客戶購買的其它商品、購買該商品的客戶看過的其它商品
- 其它的購物選擇
使用單體應用程式架構時,移動客戶端通過嚮應用程式發起一次 REST 呼叫(GET api.company.com/productdetails/)來獲取這些資料。負載均衡器將請求路由給 N 個相同的應用程式例項中的其中之一。然後,應用程式會查詢各種資料庫表,並將響應返回給客戶端。
相反,若是採用微服務架構,顯示在產品頁上的資料會分佈在不同的微服務上。下面列舉了可能與產品詳情頁資料有關的一些微服務:
- 購物車服務——購物車中的件數
- 訂單服務——歷史訂單
- 目錄服務——商品基本資訊,如名稱、圖片和價格
- 評論服務——客戶的評論
- 庫存服務——低庫存預警
- 送貨服務——送貨選項、期限和費用,這些資訊單獨從送貨方 API 獲取
- 推薦服務——推薦商品
我們需要決定移動客戶端如何訪問這些服務。讓我們看看有哪些方法。
客戶端與微服務直接通訊
從理論上講,客戶端可以直接向每個微服務傳送請求。每個微服務都有一個公開的端點(https ://.api.company.name)。該 URL 對映到微服務的負載均衡器,由後者負責在可用例項之間分發請求。為了獲取產品詳情,移動客戶端將逐一向上文列出的 N 個服務傳送請求。
遺憾的是,這種方法存在挑戰和侷限。問題之一是客戶端需求和每個微服務暴露的細粒度 API 不匹配。在這個例子中,客戶端需要傳送 7 個獨立請求。在更復雜的應用程式中,可能要傳送更多的請求;按照 Amazon 的說法,他們在顯示他們的產品頁面時就呼叫了數百個服務。然而,客戶端通過 LAN 傳送許多請求,這在公網上可能會很低效,在行動網路上就根本不可行。這種方法還使得客戶端程式碼非常複雜。
客戶端直接呼叫微服務的另一個問題是,部分服務使用的協議對 web 並不友好。一個服務可能使用 Thrift 二進位制 RPC,而另一個服務可能使用 AMQP 訊息傳遞協議。不管哪種協議對於瀏覽器或防火牆都不夠友好,最好是內部使用。在防火牆之外,應用程式應該使用諸如 HTTP 和 WebSocket 之類的協議。
這種方法的另一個缺點是,它會使得微服務難以重構。隨著時間推移,我們可能想要更改系統拆分成服務的方式。例如,我們可能合併兩個服務,或者將一個服務拆分成兩個或更多服務。然而,如果客戶端與微服務直接通訊,那麼執行這類重構就非常困難了。
由於上述三種問題的原因,客戶端直接與伺服器端通訊的方式很少在實際中使用。
使用 API 閘道器構建微服務
通常來說,使用 API 閘道器是更好的解決方式。API 閘道器是一個伺服器,也可以說是進入系統的唯一節點。這與面向物件設計模式中的 Facade 模式很像。API 閘道器封裝內部系統的架構,並且提供 API 給各個客戶端。它還可能還具備授權、監控、負載均衡、快取、請求分片和管理、靜態響應處理等功能。下圖展示了一個適應當前架構的 API 閘道器。
API 閘道器負責服務請求路由、組合及協議轉換。客戶端的所有請求都首先經過 API 閘道器,然後由它將請求路由到合適的微服務。API 閘道器經常會通過呼叫多個微服務併合並結果來處理一個請求。它可以在 web 協議(如 HTTP 與 WebSocket)與內部使用的非 web 友好協議之間轉換。
API 閘道器還能為每個客戶端提供一個定製的 API。通常,它會向移動客戶端暴露一個粗粒度的 API。以產品詳情的場景為例,API 閘道器可以提供一個端點(/productdetails?productid=xxx),使移動客戶端可以通過一個請求獲取所有的產品詳情。API 閘道器通過呼叫各個服務(產品資訊、推薦、評論等等)併合並結果來處理請求。
Netflix API 閘道器是一個很好的 API 閘道器例項。Netflix 流媒體服務提供給成百上千種類型的裝置使用,包括電視、機頂盒、智慧手機、遊戲系統、平板電腦等等。
最初,Netflix 試圖為他們的流媒體服務提供一個通用的 API。然而他們發現,由於各種各樣的裝置都有自己獨特的需求,這種方式並不能很好地工作。如今,他們使用一個 API 閘道器,通過執行與針對特定裝置的介面卡程式碼,來為每種裝置提供定製的 API。通常,一個介面卡通過呼叫平均 6 到 7 個後端服務來處理每個請求。Netflix API 閘道器每天處理數十億請求。
API 閘道器的優點和缺點
如你所料,使用 API 閘道器有優點也有不足。使用 API 閘道器的最大優點是,它封裝了應用程式的內部結構。客戶端只需要同閘道器互動,而不必呼叫特定的服務。API 閘道器為每一類客戶端提供了特定的 API,這減少了客戶端與應用程式間的互動次數,還簡化了客戶端程式碼。
API 閘道器也有一些不足。它增加了一個我們必須開發、部署和維護的高可用元件。還有一個風險是,API 閘道器變成了開發瓶頸。為了暴露每個微服務的端點,開發人員必須更新 API 閘道器。API閘道器的更新過程要儘可能地簡單,這很重要;否則,為了更新閘道器,開發人員將不得不排隊等待。不過,雖然有這些不足,但對於大多數現實世界的應用程式而言,使用 API 閘道器是合理的。
實現 API 閘道器
到目前為止,我們已經探討了使用 API 閘道器的動力及其優缺點。下面讓我們看一下需要考慮的各種設計問題。
效能和可擴充套件性
只有少數公司擁有 Netflix 這樣的規模,需要每天處理每天需要處理數十億請求。不管怎樣,對於大多數應用程式而言,API 閘道器的效能和可擴充套件性都非常重要。因此,將 API 閘道器構建在一個支援非同步、I/O 非阻塞的平臺上是合理的。有多種不同的技術可以實現一個可擴充套件的 API 閘道器。在 JVM 上,可以使用一種基於 NIO 的框架,比如 Netty、Vertx、Spring Reactor 或 JBoss Undertow 中的一種。一個非常流行的非 JVM 選項是 Node.js,它是一個基於 Chrome JavaScript 引擎構建的平臺。
另一個方法是使用 NGINX Plus。NGINX Plus 提供了一個成熟的、可擴充套件的、高效能 web 伺服器和一個易於部署的、可配置可程式設計的反向代理。NGINX Plus 可以管理身份驗證、訪問控制、負載均衡請求、快取響應,並提供應用程式可感知的健康檢查和監控。
使用響應式程式設計模型
API 閘道器通過簡單地將請求路由給合適的後端服務來處理部分請求,而通過呼叫多個後端服務併合並結果來處理其它請求。對於部分請求,比如產品詳情相關的多個請求,它們對後端服務的請求是獨立於其它請求的。為了最小化響應時間,API 閘道器應該併發執行獨立請求。
然而,有時候,請求之間存在依賴。在將請求路由到後端服務之前,API 閘道器可能首先需要呼叫身份驗證服務驗證請求的合法性。類似地,為了獲取客戶心願單中的產品資訊,API 閘道器必須首先獲取包含這些資訊的客戶資料,然後再獲取每個產品的資訊。關於 API 組合,另一個有趣的例子是 Netflix Video Grid。
使用傳統的非同步回撥方法編寫 API 組合程式碼會讓你迅速墜入回撥地獄。程式碼會變得混亂、難以理解且容易出錯。一個更好的方法是使用響應式方法,以一種宣告式樣式編寫 API 閘道器程式碼。響應式抽象概念的例子有 Scala 中的 Future、Java 8 中的 CompletableFuture 和 JavaScript 中的P romise,還有最初微軟為 .NET 平臺開發的 Reactive Extensions(RX)。Netflix 建立了 RxJava for JVM,專門用於他們的 API 閘道器。此外,還有 RxJS for JavaScript,它既可以在瀏覽器中執行,也可以在 Node.js 中執行。使用響應式方法能讓你編寫簡單但高效的 API 閘道器程式碼。
服務呼叫
基於微服務的應用程式是一個分散式系統,必須使用一種程序間通訊機制。有兩種型別的程序間通訊機制可供選擇。一種是使用非同步的、基於訊息傳遞的機制。有些實現使用諸如 JMS 或 AMQP 那樣的訊息代理,而其它的實現(如 Zeromq)則沒有代理,服務間直接通訊。
另一種程序間通訊型別是諸如 HTTP 或 Thrift 那樣的同步機制。通常,一個系統會同時使用非同步和同步兩種型別。它甚至還可能使用同一型別的多種實現。總之,API 閘道器需要支援多種通訊機制。
服務發現
API 閘道器需要知道它與之通訊的每個微服務的位置(IP 地址和埠)。在傳統的應用程式中,或許可以硬連線這個位置,但在現代的、基於雲的微服務應用程式中,這並不是一個容易解決的問題。基礎設施服務(如訊息代理)通常會有一個靜態位置,可以通過 OS 環境變數指定。但是,確定一個應用程式服務的位置沒有這麼簡單。應用程式服務的位置是動態分配的,而且,單個服務的一組例項也會隨著自動擴充套件或升級而動態變化。
總之,像系統中的其它服務客戶端一樣,API 閘道器需要使用系統的服務發現機制,可以是伺服器端發現,也可以是客戶端發現。下一篇文章將更詳細地描述服務發現。現在,需要注意的是,如果系統使用客戶端發現,那麼 API 閘道器必須能夠查詢服務註冊中心,這是一個包含所有微服務例項及其位置的資料庫。
處理區域性失敗
在實現 API 閘道器時,還需要處理區域性失敗的問題。該問題出現在所有的分散式系統中。當一個服務呼叫另一個服務,而後者響應慢或不可用的時候,就會出現這個問題。API 閘道器不能因為無限期地等待下游服務而阻塞。不過,如何處理失敗取決於特定的場景以及哪個服務失敗。例如,在產品詳情場景下,如果推薦服務無響應,那麼 API 閘道器應該向客戶端返回產品詳情的其它內容,因為它們對使用者依然有用。推薦內容可以為空,也可以用一個固定的 TOP 10 列表取代。不過,如果產品資訊服務無響應,那麼 API 閘道器應該向客戶端返回一個錯誤資訊。
如果快取資料可用,那麼 API 閘道器還可以返回快取資料。例如,鑑於產品價格不會頻繁變動,如果價格服務不可用,API 閘道器可以返回快取的價格資料。資料可以由 API 閘道器自己快取,也可以儲存在像 Redis 或 Memcached 之類的外部快取中。通過返回預設資料或者快取資料,API 閘道器可以確保系統故障不影響使用者體驗。
在編寫程式碼呼叫遠端服務方面,Netflix Hystrix 是一個格外有用的庫。Hystrix 會暫停超出特定閾限的呼叫。它實現了一個“斷路器(circuit breaker)”模式,可以防止客戶端對無響應的服務進行不必要的等待。如果服務的錯誤率超出了設定的閾值,那麼 Hystrix 會啟動斷路器,所有請求會立即失敗並持續一定時間。Hystrix 允許使用者定義一個請求失敗後的後援操作,比如從快取讀取資料,或者返回一個預設值。如果你正在使用 JVM,那麼你應該考慮使用 Hystrix;如果你正在使用一個非 JVM 環境,那麼可以使用一個功能相同的庫。
總結
對於大多數基於微服務的應用程式而言,實現 API 閘道器,將其作為系統的唯一入口很有必要。API 閘道器負責服務請求路由、組合及協議轉換。它為每個應用程式客戶端提供一個定製的 API。API 閘道器還可以通過返回快取資料或預設資料遮蔽後端服務失敗。在本系列的下一篇文章中,我們將探討服務間通訊。
下期題目:深入微服務架構的程序間通訊 ,敬請期待!
點選“Building Microservices: Using an API Gateway”,檢視英文原文
This entry was posted in 乾貨 and tagged 微服務. Bookmark the permalink.