精品專欄
當你決定將應用作為一組微服務時,需要決定應用客戶端如何與微服務互動。在單體式程式中,通常只有一組冗餘的或者負載均衡的服務提供點。在微服務架構中,每一個微服務暴露一組細粒度的服務提供點。在本篇文章中,我們來看它如何影響客戶端到服務端通訊,同時提出一種API Gateway的方法。
介紹
假定你正在為線上購物應用開發一個原生手機客戶端。你需要實現一個產品最終頁來展示商品資訊。
例如,下麵的圖展示了你在亞馬遜Android客戶端上滑動產品最終頁時看到的資訊。
雖然這是一個智慧手機應用,這個產品最終頁展示了非常多的資訊。例如,不僅這裡有產品基本資訊(名字、描述和價格),還有以下內容:
-
購物車中的物品數
-
下單歷史
-
使用者評論
-
低庫存警告
-
快遞選項
-
各式各樣的推薦,包括經常跟這個物品一起被購買的產品、購買該物品的其他顧客購買的產品以及購買該產品的顧客還瀏覽了哪些產品。
-
可選的購物選項
當採用一個單體式應用架構,一個移動客戶端將會透過一個REST請求(GET api.company.com/productdetails/productId)來獲取這些資料。一個負載均衡將請求分發到多個應用實體之一。應用將查詢各種資料庫並傳回請求給客戶端。
相對的,若是採用微服務架構,最終頁上的資料會分佈在不同的微服務上。下麵列舉了可能與產品最終頁資料有關的一些微服務:
-
購物車服務 — 購物車中的物品數
-
下單服務 — 下單歷史
-
分類服務 — 基本產品資訊,如名字、圖片和價格
-
評論服務 — 使用者評論
-
庫存服務 — 低庫存警告
-
快遞服務 — 快遞選項、截止時間、來自不同快遞API的成本計算
-
推薦服務 — 推薦產品
我們需要決定移動客戶端如何訪問這些服務。請看下麵這幾種方式
客戶端到微服務直接通訊
理論上說,一個客戶端可以直接給多個微服務中的任何一個發起請求。每一個微服務都會有一個對外服務端(https://serviceName.api.company.name)。這個URL可能會對映到微服務的負載均衡上,它再轉發請求到具體節點上。為了搜尋產品細節,移動端需要向上述微服務逐個發請求。
不幸的是,這個方案有很多困難和限制。其中一個問題是客戶端的需求量與每個微服務暴露的細粒度API數量的不匹配。如圖中,客戶端需要7次單獨請求。在更複雜的場景中,可能會需要更多次請求。例如,亞馬遜的產品最終頁要請求數百個微服務。雖然一個客戶端可以透過LAN發起很多個請求,但是在公網上這樣會很沒有效率,這個問題在移動網際網路上尤為突出。這個方案同時會導致客戶端程式碼非常複雜。
另一個存在的問題是客戶端直接請求微服務的協議可能並不是web友好型。一個服務可能是用Thrift的RPC協議,而另一個服務可能是用AMQP訊息協議。它們都不是瀏覽或防火牆友好的,並且最好是內部使用。應用應該在防火牆外採用類似HTTP或者WEBSocket協議。
這個方案的另一個缺點是它很難重構微服務。隨著時間的推移,我們可能需要改變系統微服務目前的切分方案。例如,我們可能需要將兩個服務合併或者將一個服務拆分為多個。但是,如果客戶端直接與微服務互動,那麼這種重構就很難實施。
由於上述三種問題的原因,客戶端直接與伺服器端通訊的方式很少在實際中使用。
採用一個API Gateway
通常來說,一個更好的解決辦法是採用API Gateway的方式。API Gateway是一個伺服器,也可以說是進入系統的唯一節點。這跟面向物件設計樣式中的Facade樣式很像。API Gateway封裝內部系統的架構,並且提供API給各個客戶端。它還可能有其他功能,如授權、監控、負載均衡、快取、請求分片和管理、靜態響應處理等。下圖展示了一個適應當前架構的API Gateway。
API Gateway負責請求轉發、合成和協議轉換。所有來自客戶端的請求都要先經過API Gateway,然後路由這些請求到對應的微服務。API Gateway將經常透過呼叫多個微服務來處理一個請求以及聚合多個服務的結果。它可以在web協議與內部使用的非Web友好型協議間進行轉換,如HTTP協議、WebSocket協議。
API Gateway可以提供給客戶端一個定製化的API。它暴露一個粗粒度API給移動客戶端。以產品最終頁這個使用場景為例。API Gateway提供一個服務提供點(/productdetails?productid=xxx)使得移動客戶端可以在一個請求中檢索到產品最終頁的全部資料。API Gateway透過呼叫多個服務來處理這一個請求並傳回結果,涉及產品資訊、推薦、評論等。
一個很好的API Gateway例子是Netfix API Gateway。Netflix流服務提供數百個不同的微服務,包括電視、機頂盒、智慧手機、遊戲系統、平板電腦等。起初,Netflix檢視提供一個適用全場景的API。但是,他們發現這種形式不好用,因為涉及到各式各樣的裝置以及它們獨特的需求。現在,他們採用一個API Gateway來提供容錯性高的API,針對不同型別裝置有相應程式碼。事實上,一個配接器處理一個請求平均要呼叫6到8個後端服務。Netflix API Gateway每天處理數十億的請求。
API Gateway的優點和缺點
如你所料,採用API Gateway也是優缺點並存的。API Gateway的一個最大好處是封裝應用內部結構。相比起來呼叫指定的服務,客戶端直接跟gatway互動更簡單點。API Gateway提供給每一個客戶端一個特定API,這樣減少了客戶端與伺服器端的通訊次數,也簡化了客戶端程式碼。
API Gateway也有一些缺點。它是一個高可用的元件,必須要開發、部署和管理。還有一個問題,它可能成為開發的一個瓶頸。開發者必須更新API Gateway來提供新服務提供點來支援新暴露的微服務。更新API Gateway時必須越輕量級越好。否則,開發者將因為更新Gateway而排佇列。但是,除了這些缺點,對於大部分的應用,採用API Gateway的方式都是有效的。
實現一個API Gateway
既然我們已經知道了採用API Gateway的動機和優缺點,下麵來看在設計它時需要考慮哪些事情。
效能和可擴充套件性
只有少數公司需要處理像Netflix那樣的規模,每天需要處理數十億的請求。但是,對於大多數應用,API Gateway的效能和可擴充套件性也是非常重要的。因此,建立一個支援同步、非阻塞I/O的API Gateway是有意義的。已經有不同的技術可以用來實現一個可擴充套件的API Gateway。在JVM上,採用基於NIO技術的框架,如Netty,Vertx,Spring Reactor或者JBoss Undertow。Node.js是一個非JVM的流行平臺,它是一個在Chrome的JavaScript引擎基礎上建立的平臺。一個可選的方案是NGINX Plus。NGINX Plus提供一個成熟的、可擴充套件的、高效能web伺服器和反向代理,它們均容易部署、配置和二次開發。NGINX Plus可以管理授權、許可權控制、負載均衡、快取並提供應用健康檢查和監控。
採用反應性程式設計模型
對於有些請求,API Gateway可以透過直接路由請求到對應的後端服務上的方式來處理。對於另外一些請求,它需要呼叫多個後端服務併合並結果來處理。對於一些請求,例如產品最終頁面請求,發給後端服務的請求是相互獨立的。為了最小化響應時間,API Gateway應該併發的處理相互獨立的請求。但是,有時候請求之間是有依賴的。API Gateway可能需要先透過授權服務來驗證請求,然後在路由到後端服務。類似的,為了獲得客戶的產品願望清單,需要先獲取該使用者的資料,然後傳回清單上產品的資訊。這樣的一個API 元件是Netflix Video Grid。
利用傳統的同步回呼方法來實現API合併的程式碼會使得你進入回呼函式的噩夢中。這種程式碼將非常難度且難以維護。一個優雅的解決方案是採用反應性程式設計樣式來實現。類似的反應抽象實現有Scala的Future,Java8的CompletableFuture和JavaScript的Promise。基於微軟.Net平臺的有Reactive Extensions(Rx)。Netflix為JVM環境建立了RxJava來使用他們的API Gateway。同樣地,JavaScript平臺有RxJS,可以在瀏覽器和Node.js平臺上執行。採用反應程式設計方法可以幫助快速實現一個高效的API Gateway程式碼。
服務呼叫
一個基於微服務的應用是一個分散式系統,並且必須採用執行緒間通訊的機制。有兩種執行緒間通訊的方法。一種是採用非同步機制,基於訊息的方法。這類的實現方法有JMS和AMQP。另外的,例如Zeromq屬於服務間直接通訊。還有一種執行緒間通訊採用同步機制,例如Thrift和HTTP。事實上一個系統會同時採用同步和非同步兩種機制。由於它的實現方式有很多種,因此API Gateway就需要支援多種通訊方式。
服務發現
API Gateway需要知道每一個微服務的IP和埠。在傳統應用中,你可能會硬編碼這些地址,但是在現在雲基礎的微服務應用中,這將是個簡單的問題。基礎服務通常會採用靜態地址,可以採用作業系統環境變數來指定。但是,探測應用服務的地址就沒那麼容易了。應用服務通常動態分配地址和埠。同樣的,由於擴充套件或者升級,服務的實體也會動態的改變。因此,API Gateway需要採用系統的服務發現機制,要麼採用服務端發現,要麼是客戶端發現。後續的一篇文章將會更詳細的介紹這部分。如果採用客戶端發現服務,API Gateway必須要去查詢服務註冊處,也就是微服務實體地址的資料庫。
處理部分失敗
在實現API Gateway過程中,另外一個需要考慮的問題就是部分失敗。這個問題發生在分散式系統中當一個服務呼叫另外一個服務超時或者不可用的情況。API Gateway不應該被阻斷並處於無限期等待下游服務的狀態。但是,如何處理這種失敗依賴於特定的場景和具體服務。例如,如果是在產品詳情頁的推薦服務模組無響應,那麼API Gateway應該傳回剩下的其他資訊給使用者,因為這些資訊也是有用的。推薦部分可以傳回空,也可以傳回固定的頂部10個給使用者。但是,如果是產品資訊服務無響應,那麼API Gateway就應該給客戶端傳回一個錯誤。
在快取有效的時候,API Gateway應該能夠傳回快取。例如,由於產品價格變化並不頻繁,API Gateway在價格服務不可用時應該傳回快取中的數值。這類資料可以由API Gateway自身來快取,也可以由Redis或Memcached這類外部快取實現。透過傳回快取資料或者預設資料,API Gateway來確保系統錯誤不影響到使用者體驗。
Netflix Hystrix對於實現遠端服務呼叫程式碼來說是一個非常好用的庫。Hystrix記錄那些超過預設定的極限值的呼叫。它實現了circuit break樣式,使得可以將客戶端從無響應服務的無盡等待中停止。如果一個服務的錯誤率超過預設值,Hystrix將中斷服務,並且在一段時間內所有請求立刻失效。Hystrix可以為請求失敗定義一個fallback操作,例如讀取快取或者傳回預設值。如果你在用JVM,就應該考慮使用Hystrix。如果你採用的非JVM環境,那麼應該考慮採用類似功能的庫。
總結
對於大多數微服務基礎的應用,實現一個API Gateway都是有意義的,它就像是進入系統的一個服務提供點。API Gateway負責請求轉發、請求合成和協議轉換。它提供給應用客戶端一個自定義的API。API Gateway可以透過傳回快取或者預設值的方式來掩蓋後端服務的錯誤。在本系列的下一篇文章中,我們將討論服務間的通訊問題。