使用API網關構建微服務

jopen 10年前發布 | 100K 次閱讀 微服務 WEB服務/RPC/SOA

 

有關微服務的系列文章的 第一篇 介紹了微服務架構模式,討論了使用微服務的優缺點,以及為什么它們雖然復雜度高卻常常是復雜應用程序的理想選擇。

當選擇將應用程序構建為一組微服務時,需要確定應用程序客戶端如何與微服務交互。在單體應用程序中,只有一組(通常是重復的、負載均衡的)端點。然而,在 微服務架構中,每個微服務都會暴露一組通常是細粒度的端點。在本文中,我們將討論一下這對客戶端與應用程序之間的通信有什么影響,并提出一種使用 API網關 的方法。

引言

讓我們想象一下,你要為一個購物應用程序開發一個原生移動客戶端。你很可能需要實現一個產品詳情頁面,上面展示任何指定產品的信息。

例如,下圖展示了在Amazon Android移動應用中滾動產品詳情時看到的內容。

使用API網關構建微服務

雖然這是個智能手機應用,產品詳情頁面也顯示了大量的信息。例如,該頁面不僅包含基本的產品信息(如名稱、描述、價格),而且還顯示了如下內容:

  • 購物車中的件數
  • 訂單歷史
  • 客戶評論
  • 低庫存預警
  • 送貨選項
  • 各種推薦,包括經常與該產品一起購買的其它產品,購買該產品的客戶購買的其它產品,購買該產品的客戶看過的其它產品。
  • 可選的購買選項。

當使用單體應用程序架構時,移動客戶端將通過向應用程序發起一次REST調用(GET api.company.com/productdetails/<productId>)來獲取這些數據。負載均衡器將請求路由給N個相同 的應用程序實例中的一個。然后,應用程序會查詢各種數據庫表,并將響應返回給客戶端。

相比之下,當使用微服務架構時,產品詳情頁面顯示的數據歸多個微服務所有。下面是部分可能的微服務,它們擁有要顯示在示例中產品詳情頁面上的數據:

  • 購物車服務——購物車中的件數
  • 訂單服務——訂單歷史
  • 目錄服務——產品基本信息,如名稱、圖片和價格
  • 評論服務——客戶的評論
  • 庫存服務——低庫存預警
  • 送貨服務——送貨選項、期限和費用,這些單獨從送貨方的API獲取
  • 推薦服務——建議的產品

使用API網關構建微服務

我們需要決定移動客戶端如何訪問這些服務。讓我們看看都有哪些選項。

客戶端與微服務直接通信

從理論上講,客戶端可以直接向每個微服務發送請求。每個微服務都有一個公開的端點(https ://<serviceName>.api.company.name)。該URL將映射到微服務的負載均衡器,由它負責在可用實例之間分發 請求。為了獲取產品詳情,移動客戶端將逐一向上面列出的N個服務發送請求。

遺憾的是,這種方法存在挑戰和局限。一個問題是客戶端需求和每個微服務暴露的細粒度API不匹配。在這個例子中,客戶端需要發送7個獨立請求。在 更復雜的應用程序中,可能要發送更多的請求。例如,按照Amazon的說法,他們在顯示他們的產品頁面時就調用了數百個服務。然而,客戶端通過LAN發送 許多請求,這在公網上可能會很低效,而在移動網絡上就根本不可行。這種方法還使得客戶端代碼非常復雜。

客戶端直接調用微服務的另一個問題是,部分服務使用的協議不是Web友好協議。一個服務可能使用Thrift二進制RPC,而另一個服務可能使用 AMQP消息傳遞協議。不管哪種協議都不是瀏覽器友好或防火墻友好的,最好是內部使用。在防火墻之外,應用程序應該使用諸如HTTP和WebSocket 之類的協議。

這種方法的另一個缺點是,它會使得微服務難以重構。隨著時間推移,我們可能想要更改系統劃分成服務的方式。例如,我們可能合并兩個服務,或者將一個服務拆分成兩個或更多服務。然而,如果客戶端與微服務直接通信,那么執行這類重構就非常困難了。

由于這些問題的存在,客戶端與微服務直接通信很少是合理的。

使用API網關

通常,一個更好的方法是使用所謂的 API網關 。API網關是一個服務器,是系統的唯一入口。從面向對象設計的角度看,它與 外觀模式 類似。API網關封裝了系統內部架構,為每個客戶端提供一個定制的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存、“請求整形(request shaping)”與管理、靜態響應處理。

下圖展示了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中的 Promise ,還有最初是微軟為.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網關還可以通過返回緩存數據或默認數據屏蔽后端服務失敗。在本系列的下一篇文章中,我們將探討服務間 通信。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!