1. 程式人生 > >第二篇:使用API閘道器

第二篇:使用API閘道器

寫在前面的話,這些文章是在NGINX的官方部落格中發現的。是關於微服務的一系列的文章,本著好東西共享一下,同時也豐富一下自己,把這些翻譯成中文,但是後來發現國內已經有很多人翻譯了,我只能說我的品位還不差,和各位大牛步調還算一致,雖然已經有人翻譯了,但是本著“一千個讀者就有一千個哈姆雷特”的想法繼續了下去。

第一篇文章介紹瞭如何設計、構建和部署微服務。其中討論了使用微服務的優點和缺點,即使由於微服務的複雜性,對於複雜的應用來說,它們通常情況下也是理想的選擇。這篇文章,也是本系列文章的第二篇,將會討論使用API閘道器構建微服務。

當你選擇將你的應用構建成一系列的微服務的時候,你就需要考慮你的應用客戶端如何與微服務進行互動。對於一個單體應用來說,只要有一系列的endpoint

即可,然後可以通過負載均衡將流量分佈到完全相同的應用中即可。但是在微服務架構中,每個服務都會有很多細粒度的endpoint。本文將要討論的就是這種情況如何影響客戶端與應用的通訊,並且提出使用API閘道器的方法。

一、介紹

假如你正在開發一個購物的移動應用客戶端,很可能要實現一個產品詳細資訊的頁面,它會展示一個產品的詳細資訊。例如圖2-1顯示了在Amazon的Android客戶端瀏覽產品資訊時看到的頁面:

這裡寫圖片描述

圖2-1 購物應用

雖然這是一個智慧手機應用,但是產品資訊頁面仍然要展現很多的資訊。例如,基本的產品資訊,比如名稱、描述、價格,該頁也會展示:

  1. 購物車中的產品數量;
  2. 歷史訂單;
  3. 顧客評論;
  4. 低庫存預警;
  5. 運送選項;
  6. 各種推薦,包括與此產品時同時購買的其他產品,購買該產品的顧客也購買的產品,購買該產品的顧客瀏覽的產品;
  7. 可替代的購買選項;

當使用單體應用架構時,移動客戶端通過給應用傳送一個REST請求來獲取資料,比如:

GET api.company.com/productdetails/productId

負載均衡器路由這個請求到幾個相同應用例項其中的一個。單體應用接著查詢多個數據庫表並返回響應給客戶端。

比較而言,當使用微服務架構的時候,顯示在產品詳細資訊頁面的資料來自於多個微服務。如下是一些潛在的服務,它們也有可以展示在特定產品資訊頁面的資料:

  • 購物車服務—購物車中產品的數量
  • 訂單服務—歷史訂單
  • 目錄服務—產品的基本資訊,比如產品名稱、圖片和價格
  • 評論服務—客戶的評論
  • 庫存服務—低庫存預警
  • 運送服務—從物流提供商的API得到的運送選項、期限和價格
  • 推薦服務—建議的商品

這裡寫圖片描述

圖2-2 對映移動客戶端到相應的微服務

我們需要考慮移動客戶端如何訪問這些服務,瞭解可能的選項。

二、客戶端和微服務直接通訊

理論上,客戶端可以直接請求每個微服務。每個微服務需要有一個公共的endpoint

這個URL將會對映到微服務的負載均衡器,這個負載均衡器會將請求分發到可用的例項上,為了獲取特定產品的頁面資訊,移動客戶端會向上述列出的每個服務傳送請求。

不幸的是,對於這種選擇也有很大的限制和挑戰。一個問題是客戶端的需求和每個微服務暴露出來的細粒度的API不相符。在本例中,客戶端必須傳送7次單獨的請求。在更復雜的應用中,可能傳送更多的請求。例如,Amazon描述了上百的服務是如何參與到它們的產品頁面的渲染的。即使客戶端通過區域網傳送很多的請求,當通過公網的時候也會變得效率低下,通過行動網路更是不切實際。這種方法也使得客戶端程式碼更復雜。

直接呼叫客戶端的另外一個問題是,一些服務可能使用了web不友好的協議。一個服務可能使用了Thrift 二進位制RPC ,另外的服務可能使用AMQP訊息協議。這些協議對瀏覽器和防火牆都不是友好的,比較適合內部使用。應用應該在防火牆外部使用類似於HTTPWebSocket的協議。

使用這種方法的另外一個缺點是會使重構微服務變得非常困難。隨著時間的發展,我們可能改變系統劃分服務的方法。例如,我們可能融合兩個服務或者將一個服務分成兩個或者多個服務。但是如果客戶端直接和服務進行通訊,那麼完成這種重構會變得異常困難。

因為這些問題,很少會讓客戶端直接和微服務通訊。

三、使用API閘道器

通常情況下一個更好的方法是使用API閘道器。API閘道器是一個伺服器,可以作為訪問系統的唯一入口。與面向物件設計中的門面設計模式類似。API閘道器封裝了內部系統架構併為每個客戶端量身定製了API。它也可能擔負其他的責任,比如認證、監控、負載均衡、快取、流量整形、管理和靜態資源處理。

圖2-3顯示了API閘道器如何融入微服務架構中。

這裡寫圖片描述

API閘道器負責請求路由、組裝以及協議翻譯。所有來自客戶端的請求首先進入API閘道器。接著閘道器會將該請求路由到合適的微服務中。API閘道器也可以通過呼叫多個微服務以及合併結果來處理請求。它可以在web協議比如HTTPWebSocket、內部使用的對web不友好的協議之間轉換。

API閘道器也能為每個客戶端提供定製的API。它通常向移動客戶端暴露出一個粗粒度的API。例如產品詳細資訊場景中。API閘道器可以提供一個endpoint(/productdetails?productid=xxx),可以使移動客戶端在一次呼叫中獲取所有的產品資訊。API閘道器通過呼叫多個服務—產品服務、推薦服務、評論服務等來處理請求,並封裝結果。

API閘道器的一個優秀的例項是Netflix API Gateway。Netflix的流服務在上百種裝置上,比如電視、機頂盒、手機、遊戲系統、平板電腦等,都能夠流暢執行。開始的時候,Netflix嘗試為他們的流服務提供one-size-fts-all API。然而,他們發現由於裝置種類繁多,需求各異,所以這種方式無法正常工作。現在,它們使用API閘道器通過執行特定裝置介面卡的程式碼來給每個裝置提供定製的API。一個介面卡可以通過呼叫,平均情況下6-7個後端的服務來處理每個請求。Netflix的API閘道器每天處理數十億的請求。

四、API閘道器的優點和缺點

正如你所料,API閘道器的優點和缺點同時存在。一個主要的優點是它封裝了應用的內部結構。客戶端直接和閘道器通訊,而不必呼叫特定的服務。這種方式提供給每種客戶端特定的API,減少了客戶端和應用之間的往返次數,同時也簡化了客戶端的程式碼。

API閘道器也有一些缺點。它必須被開發、部署和管理成一個高度可用的元件,也有成為開發瓶頸的危險。為了暴露出每個微服務的endpoint開發者必須更新API閘道器

儘可能輕量級地更新API閘道器是很重要的。否則,開發者將會被迫排隊等待閘道器更新。即使存在這些缺點,對於現實世界的應用,使用API閘道器仍然是行之有效的方法。

五、實現API閘道器

既然我們已經瞭解了使用API閘道器的動機和要進行的取捨,就需要考慮一下各種設計上的問題。

5.1 效能與擴充套件性

很少公司能達到Netflix需要每天處理數以十億的請求的規模。但是對於大多數的應用來說,API閘道器的擴充套件性仍然是非常重要的。因此在一個支援非同步、非阻塞IO的平臺上構建API閘道器是很必要的。有很多技術可以用來實現可擴充套件的API閘道器。在JVM上你可以選擇基於NIO的框架,比如Netty、Vertx、Spring Reactor或者JBoss Undertow。一個流行的非JVM選項是Node.js,它是構建在Chrome的JavaScript引擎之上。另一個選擇是使用NGINX Plus。NGINX Plus提供了成熟的、可擴充套件的、高效能的web伺服器和易於部署、配置和程式設計的反向代理伺服器。NGINX Plus可以管理認證、訪問控制、負載均衡、快取響應,提供應用級別的健康檢查和監控。

5.2 使用響應式程式設計模型

API閘道器通過將請求簡單地路由到合適的後端服務上來處理。通過呼叫多個後端的服務並組裝結果來處理其他請求。因為這些請求,比如產品資訊請求,到後端的時候是獨立於其他的請求的。為了將響應時間減少到最短,API閘道器應該並行地執行獨立的請求。

然而有時,請求之間存在依賴。在路由請求到後端服務前,API閘道器可能需要先通過許可權服務驗證。類似的,為了獲取顧客願望單中的產品資訊,API閘道器必須首先獲取顧客的願望單資訊,接著獲取每個產品的資訊。另外有關組織API的一個有趣的例子是Netflix Video Grid

使用傳統的非同步回撥方法來寫API組合程式碼會使你很快進入回撥的地獄。程式碼會變得彼此糾纏、理解困難、易於出錯。寫API閘道器程式碼的較好的辦法是通過宣告的方式使用響應式方法。響應式的抽象例子包括Scala中的FutureJava 8中的CompletableFutureJavaScript中的Promise。也存在一些響應式的擴充套件(也叫做Rx或者ReactiveX),這些本來是微軟為.NET平臺開發的。Netflix為基於JVM的環境創造了RxJava以便在它們的API閘道器中使用。JavaScript中的RxJS可以在瀏覽器和Node.js中執行。使用響應式的方法可以讓你的API閘道器的程式碼寫得更加簡單而高效。

5.3 服務呼叫

基於微服務的應用是一個分散式系統,必須使用程序間通訊機制。兩種程序間通訊方式如下:

  • 一種是使用非同步的,基於訊息的機制。一些實現使用了訊息代理,比如JMS或者AMQP。其他的比如Zeromq,是無代理的,服務之間直接通訊。
  • 另外一種方式是同步機制,比如HTTP或者Thrift

一個系統可以結合使用同步和非同步,甚至可以使用每種方式的多種實現。結果,API閘道器會需要支援多種通訊機制。

5.4 服務發現

API閘道器需要知道要與之進行通訊的每個微服務的位置(IP地址和埠)。在傳統的應用中,你可能硬編碼這個地址,但是在現在、基於雲的微服務應用中,發現需要服務的位置不是簡單的事情。

基礎服務,比如訊息代理,通常情況下都會有一個靜態地址,可以通過系統環境變數指定。然而確定應用服務的位置卻是不容易的。

應用服務具有動態分配的地址。由於動態伸縮和更新,一個服務的例項可能動態改變。API閘道器會像系統中的其他客戶端,需要使用系統的服務發現機制,或者是服務端發現或者是客戶端發現。後面的文章會對服務發現進行詳細的說明。到目前為止,值得注意的是如果系統使用了客戶端發現機制,API閘道器必須能夠查詢服務登錄檔,這是一個儲存有所有微服務例項和它們的位置的資料庫。

5.5 處理區域性故障

另外一個你需要考慮的問題是區域性故障。這個問題在所有的分散式應用中,當一個服務呼叫其他的服務時響應很慢或者不可用時,都有可能發生。API閘道器不應該無限期阻塞在等待下游服務的狀態中。然而,如何處理故障取決於特定的場景和不可用的服務。例如,如果推薦服務在產品詳細資訊場景下無響應,API閘道器應該返回其餘的產品資訊,因為它們對於客戶來說仍然是有用的。這些推薦可以是空的,或者是可以被,例如,固定的前十名代替。然而如果產品資訊服務沒有響應,API閘道器服務應該返回一個錯誤給客戶端。

如果可用的話,API閘道器也可以返回快取的資料。例如,如果產品價格不經常變化,當價格服務不可用的時候,API閘道器就可以返回快取的價格資料。這個資料可以被API閘道器快取,也可以被外部的快取系統儲存,例如Redis或者Memcached。通過返回預設的資料或者快取的資料,API閘道器確保系統故障對使用者體驗的影響最小。

當編碼呼叫遠端服務的時候,Netflix Hystrix是一個令人難以置信的庫。Hystrix 將超過指定閾值的呼叫設定為超時。它實現了斷路器模式circuit breaker pattern),這終止了客戶端對無響應服務的不必要等待。如果針對某個服務的錯誤率超過了指定的閾值,Hystrix會觸發斷路器,此時所有的請求在經歷了一段時間後會迅速失敗。當請求失敗的時候,Hystrix讓你定義了備選項,比如從快取中讀取資料或者返回預設值。如果你使用JVM,你當然應該考慮使用Hystrix。如果你的應用執行在一個非JVM的環境中,你也應該使用類似的庫。

六、總結

對於大多數基於微服務的應用,實現API閘道器是很重要的,它作為進入系統的唯一入口,負責請求路由、組裝、協議翻譯。提供給每個應用客戶端特定的API。通過返回快取或者預設值來隱藏後端服務的故障。在後面的文章中,我們將繼續瞭解服務間的通訊。