java—構建微服務:API網關的實現

一丶 簡介

假設你正在為購物應用開發一個手機客戶端,好像你需要實現一個產品詳情頁,用來展示任何給定的產品的詳細信息。

舉個例子,下圖展示了你在Amazon的android手機客戶端上滾動看到的產品詳情頁。

java—構建微服務:API網關的實現

Amazon手機客戶端上的產品詳情頁

即使這是一個智能手機上的應用,產品詳情頁一樣展示了很多信息。例如,不僅有基本的產品信息(如名稱、描述和價格),而且這個頁面還展示了:

  • 購物車中的商品數量

  • 歷史訂單數

  • 用戶評論

  • 低庫存的預警

  • 發貨選項

  • 各種推薦,包括經常和本產品一起被購買的其他產品,購買該產品的顧客購買的其他產品,還有購買該產品的顧客查看的其他產品

當你使用單體應用架構時,手機客戶端可以簡單地通過一個REST接口檢索到該數據。一個負載均衡器將請求路由到N個相同的應用實例中的一個。然後這個應用實例查詢多張數據庫表,最後將數據響應給客戶端。

相反,當我們使用微服務架構之後,在產品詳情頁上展示的數據來自多個微服務。這裡是一些可能擁有產品詳情頁數據的微服務:

  • 購物車服務 - 購物車中物品的數量

  • 訂單服務 - 歷史訂單

  • Catalog 服務 - 產品基本信息,如名稱、圖片和價格

  • 評論服務 - 用戶的評論

  • 庫存服務 - 低庫存預警

  • 郵寄服務 - 發貨選項,期限,來自各個郵寄提供商的API的成本

  • 推薦服務 - 推薦商品

java—構建微服務:API網關的實現

來自多個微服務的數據

我們需要決定手機客戶端如何訪問這些服務,讓我們看看有幾種選擇。

二丶 客戶端和微服務直接交互

理論上來說,客戶端可以直接請求每個微服務,每個微服務都有一個公共的端點(https://serviceName.api.company.name)。這個URL會被映射到微服務的負載均衡器上,然後它再分發請求到可用的應用實例上。為了檢索產品詳情,客戶端需要向上面列出的所有微服務發送請求。

可惜,這種方法實現起來是有困難和限制的。一個問題是,客戶端需求和每個微服務暴露的細粒度的API是不匹配的。在這個例子中,客戶端需要分別發送七次請求,在更復雜的應用中可能需要請求更多次。例如,Amazon介紹說,在他們的產品詳情頁上涉及到數百個微服務。雖然客戶端在局域網上可以發起這麼多的請求,但是在公網上可能效率太低,這在移動網絡上是不符合實際的。而且這種方法也會使客戶端的代碼特別複雜。

客戶端直接調用微服務的另外一個問題是,一些微服務使用的協議不是web友好的。一個服務使用的是Thrift RPC的二進制協議,而另一個服務可能使用的是AMQP消息協議,這兩個協議對瀏覽器和防火牆都是不友好的,最好是在內部使用。一個應用應該使用例如 HTTP 和 WebSocket 這樣能穿透防火牆的協議。

這個方法的另外一個缺點是,它會使微服務的重構比較苦難。隨著時間的推移,我們可能需要將某個系統拆分成服務。例如,我們可能將兩個服務合併成一個,或者將一個服務拆分成兩個甚至更多的服務。不管怎樣,如果客戶端和很多服務之間都是直接通信,這樣的重構可能是極端困難。

由於以上這些問題,客戶端很少會和微服務直接通信。

三丶API網關

通常情況下,更好的方法是使用所謂的API網關。API網關是一個系統的一個入口,它類似於面向對象設計中的外觀模式。API網關封裝了內部系統架構,為每個客戶端單獨提供一個API。API網關可能還有其他的職責,例如授權、監控、負載均衡、緩存、請求的修改和管理、靜態響應的處理。

下圖展示了一個API網關通常適合的架構:

java—構建微服務:API網關的實現

API網關所適合的架構

API網關負責請求的路由、組合和協議轉換,所有來自客戶端的請求首先都要經過API網關,然後它再路由請求到合適的微服務。API網關處理請求的方式通常是,調用多個微服務,然後合併響應結果。API網關可以在web協議(如HTTP、WebSocket)和內部使用的web不友好的協議之間做轉換。

API網關也可以為每個客戶端提供一個特定的API,它通常會為手機客戶端暴露一些粗粒度的API,例如在產品詳情的場景下,API網關可以提供一個端點(/productdetails?productid=xxx),這樣手機客戶端發送一個請求就能檢索到產品的所有信息。API網關處理這個請求調用了多個服務(產品信息、推薦、評論等),然後合併響應結果。

Netflix API 網關是一個非常好的例子,Netflix的流服務支撐著數百種設備,包括電視、機頂盒、智能手機、遊戲系統、平板電腦等等。最初,Netflix試圖給所有的設備提供統一的API服務,然而他們發現效果不是很好,因為各種各樣的設備都有獨特的需求。現在他們使用API網關為每種設備提供定製化的API,通過執行特定設備的適配器代碼實現的。通常每個適配器處理一個請求平均要調用後端六七個服務。Netflix的API網關每天處理數十億的請求。

四丶API網關的優點和缺點

正如你期待的那樣,API網關既有優點也有缺點。使用API網關的一個主要優點就是,它封裝了應用的內部架構,客戶端不需要調用特定的服務,而只需要與網關通信。API網關給每一種客戶端都提供了特定的API,這簡化了客戶端與應用之間的通信往返次數,也簡化了客戶端代碼。

API網關也有一些缺點,這又是一個高可用的組件,需要開發、部署和管理。API網關成為開發的瓶頸,這也是有風險的。開發人員為了暴露每個微服務的端點必須更新API網關,更新API網關的過程儘量輕量化也是很重要的,否則開發人員將在更新API網關的過程上被迫排隊。儘管有這麼多的缺點,但是在現實中的應用上使用API網關還是有意義的。

五丶API網關的實現

我們已經看到了使用API網關的動機和一些利弊權衡,現在讓我們看一下你需要考慮的各種設計問題。

性能和伸縮性

只有少數的公司有Netflix的運營規模,每天需要處理數十億的請求,然而對於大多數的應用程序來說,API網關的性能和伸縮性是非常重要的。因此,在構建API網關的時候使用異步調用和非阻塞I/O是非常有意義的。有很多種不同的技術都可以用來實現可伸縮的API網關,在JVM平臺中你可以使用Netty、Vertx、Spring Reactor 或者 JBoss Undertow等這些機遇NIO的框架,在非JVM的平臺中流行的技術是Node.js,它是運行在chrome的JavaScript引擎中的,另一個選擇是Nginx Plus,Nginx Plus提供了一個成熟的、可擴展的、高性能的Web服務器,並且還提供了易於部署、配置和編程的反向代理,Nginx Plus可以管理授權、訪問控制、請求的負載均衡、響應的緩存,並提供了應用本身的健康檢查和監控。

使用響應式編程模型

API網關處理一些請求的方式是,簡單將他們路由到合適的後端服務;處理其它請求的方式是,調用多個後端服務,然後將它們的響應結果聚合在一起。對於某些請求,如產品詳情的請求,它的後端服務都是彼此獨立的,為了減少響應時間,API網關應該並行地執行這些獨立的請求。然而,有時候一些請求之間是彼此依賴的。API網關在將請求路由到後端服務之前,可能需要調用身份驗證服務來驗證請求。類似地,在獲取顧客需要的產品列表時,API網關必須首先檢索顧客需要的產品概要信息,然後才能檢索每個產品的詳細信息。另外一個有趣的API組合的示例是 Netflix Video Grid。

使用傳統的異步回調的方法編寫API組合的代碼,很快就會將你帶到回調的地獄,代碼將會變得混亂、難於理解並且易於出錯。一個更好的實現API網關的方法是,使用響應式編程方法來實現聲明式的API網關代碼。響應式概念的例子有Scala中的Future,Java 8 中的 CompletableFuture,JavaScript中的 Promise。也還有Reactive Extensions,也叫 Rx 或者 ReactiveX,最初是微軟為 .Net 平臺開發的;Netflix創建了JVM平臺的 RxJava,並將它使用在他們的API網關上;也有 JavaScript 上的 RxJS,它運行在瀏覽器或者Node.js上。使用響應式方法編寫API網關的代碼簡單又高效。

服務調用

基於微服務的應用是一個分佈式系統,必須使用進程間通信機制。進程間通信有兩種方式:一種是異步的、基於消息的機制,有些是用消息中間件(JMS or AMQP)實現的,其它的是直接與服務通信的無中間件模式,如Zeromq;進程間通信的另外一種方式是採用同步機制,如HTTP或者Thrift。通常一個系統會同時使用同步和異步方式,甚至會使用每種方式的不同實現形式,因此API網關必須支持多種通信機制。

服務發現

API網關需要知道要調用的每個微服務的位置(IP地址和端口號),在傳統的應用中,你可能需要硬性地配置各個微服務的位置,但現在的基於雲的微服務應用中就很簡單了。基礎設施服務,例如消息中間件,通常都是一個靜態的位置,可以通過操作系統的環境變量來指定。然而,確定一個應用服務的位置不是那麼簡單的,應用服務是動態分配位置的,並且一個服務的實例集合也是動態改變的,這是因為一些自動的擴縮容和升級。因此,API網關要像其他的服務客戶端一樣,需要使用系統的服務發現機制:Server‑Side Discovery 或者 Client‑Side Discovery。在後面的文章中,將詳細地介紹服務發現。現在需要我們注意的是,如果系統使用的是客戶端側的發現,API網關必須能夠查詢到服務註冊中心,它是所有微服務實例和對應位置的數據庫存儲。

部分失敗的處理

在實現API網關的時候必須解決的一個問題是部分失敗問題。這個問題在所有的分佈式系統中都會出現,因為一個服務調用另外的服務時有可能響應慢或者服務不可用。API網關決不能由於等待下游服務而被無限期的阻塞下去,例如在產品詳情的場景中,如果推薦服務未響應,API網關應該將其餘的產品詳情信息返回給客戶端,因為這些東西仍然對用戶是有用的,這時的推薦內容是空的或者被其他的內容代替,例如top 10的產品。但是如果產品信息服務未響應,API網關應該返回錯誤給客戶端。

如果後端服務不可用,API網關也可以返回緩存數據,例如,因為產品價格是很少改變的,如果產品價格服務不可用,API網關可以返回緩存的價格數據。數據可以被緩存在API網關本身,也可以緩存在外部,如Redis或者Memcached。API網關在後端系統調用失敗的時候通過返回默認數據和緩存數據來確保不影響用戶體驗。

Netflix Hystrix是一個在調用遠程服務時非常有用的編碼庫,Hystrix調用時間超過某個設定的閾值,就是所謂的超時,它是實現了斷路器模式的,這時它會阻止客戶端對不響應的服務的不必要等待。如果一個服務的錯誤率超過指定的閾值,然後Hystrix就會觸發斷路器,然後所有的請求在一個時間區間內都會立即失敗。Hystrix會讓你定義一個請求失敗後的返回函數,例如從緩存讀取數據或者返回默認值。如果你正在使用JVM的平臺,你應該考慮使用Hystrix。如果在使用非JVM平臺你應該等效的類庫。

總結

到這裡,java—構建為服務:API網關的實現就結束了,不足之處還望大家多多包涵!!覺得收穫的話可以點個關注收藏轉發一波喔,謝謝大佬們支持。(吹一波,233~~)

下面和大家交流幾點編程的經驗:

1、多寫多敲代碼,好的代碼與紮實的基礎知識一定是實踐出來的

2丶 測試、測試再測試,如果你不徹底測試自己的代碼,那恐怕你開發的就不只是代碼,可能還會聲名狼藉。

3丶 簡化算法,代碼如惡魔,在你完成編碼後,應回頭並且優化它。從長遠來看,這裡或那裡一些的改進,會讓後來的支持人員更加輕鬆。

最後,每一位讀到這裡的網友,感謝你們能耐心地看完。希望在成為一名更優秀的Java程序員的道路上,我們可以一起學習、一起進步。

內部交流群469717771 歡迎各位前來交流和分享, 驗證:(009)

java—構建微服務:API網關的實現

相關推薦

推薦中...