1. 程式人生 > >構建微服務之使用API閘道器

構建微服務之使用API閘道器

對於設計、構建和部署微服務系列七篇文章的第一篇,我們介紹了微服務架構風格,討論了微服務的優勢和劣勢,儘管微服務有些複雜,但仍然是構建複雜應用的一個明智選擇,第二篇文章將討論使用API閘道器構建微服務。

當我們選擇把應用構建成一組微服務的時候,我們需要決定應用的客戶端如何與這些微服務進行互動。傳統單體應用中,往往只是一組(一般是replicated,負載均衡)的節點,而在微服務架構中,每個微服務都會暴露一組細粒度的節點。這篇文章中,我們將檢驗這種方式如何影響客戶端與應用端的通訊,並且提出使用API閘道器的方式來解決這個問題。

介紹

假設我們正在為一個商品應用開發一個原生移動客戶端,我們應該提供一個產品明細頁來展示指定產品的資訊。
正如下圖所示,當我們在亞馬遜的安卓移動應用中滾動產品明細頁時,它將會呈現給我們:

儘管這是移動應用,產品明細頁依然展示給我們很多資訊,比如它不僅僅展示了產品的基本資訊(比如名稱、描述、價格等),還展示了:

  • 購物車中的條目數
  • 訂單歷史記錄
  • 使用者點評
  • 低庫存預警
  • 配送選項
  • 各項推薦,包括購買本產品還經常一起購買了其它某產品,客戶買了這個產品同時還買了其他某產品,購買該產品的使用者還瀏覽了哪些產品
  • 替代購買選項

當我們使用單體架構模式的時候,一個移動客戶端可能通過傳送單一的REST呼叫請求(GET api.company.com/productdetails/productId) 來獲取展示的資料,負載均衡器會把該請求路由到多個相同應用例項的其中一臺,應用繼續查詢不同的資料庫表並返回請求資料給客戶端。

對應的,在使用微服務架構模式的時候,需要在產品明細頁展示的資料被多個微服務所擁有,下面是一些可能擁有需要展示資料在產品明細頁的微服務:

  • 購物車服務:購物車中的產品條目
  • 訂單服務:訂單歷史
  • 目錄服務:基本產品資訊,比如名稱、圖片、價格等
  • 點評服務:使用者點評
  • 庫存服務:低庫存預警
  • 配送服務:配送選項、時限以及來自配送提供者API計算出的費用
  • 推薦服務:建議購買項

我們需要決定移動端如何訪問這些服務,先看下面的選項:

客戶端直接與微服務通訊

理論上客戶端可以直接與每一個微服務進行通訊,每個微服務將會有一個公開的節點(https://serviceName.api.company.name**)),這個URL將會對映到負載均衡器,然後被分發到可用的例項上被處理,為了獲取產品明細,移動客戶端需要向上面列出的各個微服務傳送請求。

非常不幸的是,這種方案有諸多挑戰和限制,問題之一就是客戶端與每個微服務暴露出的細粒度API之間的不匹配,本例子中的客戶端需傳送七個不同的請求,在一個更加複雜的應用中請求數可能更多,比如亞馬遜在渲染產品頁的時候可能要呼叫上百個服務來渲染頁面,一個客戶端可以在LAN中傳送多個請求,但是在公網上就特別低效,那就不用提在移動裝置上了,當然,這種方式也使得客戶端異常的複雜。

客戶端直接呼叫微服務的另一個問題是,一些服務可能使用對web並不友好的協議實現。一個服務可能使用Thrift二進位制的RPC而另一個服務可能使用AMQP訊息協議。這些協議都不是瀏覽器和防火牆友好的,最好是在應用內部被使用。防火牆之外呢,應用最好使用HTTP或者WebSocket。

這種方式另一個劣勢是使得微服務重構變得困難,隨著時間推移,我們可能需要重新劃分、組織微服務,比如我們可能合併兩個微服務,也可能把某微服務拆分為兩個或多個,如果客戶端直接與微服務互動的話,對這些微服務進行重構變得異常困難。

正是由於這些問題,採用客戶端直接呼叫微服務的方式並不明智。

使用API閘道器

通常更好的方式是使用大家都熟知的API閘道器,API閘道器是提供系統唯一入口的一臺伺服器,它和麵向物件設計模式中的門面類似:API閘道器封裝了內部的系統架構並向每個客戶端提供裁剪的API,它也可能負責諸如使用者驗證、監控、負載均衡、快取、請求改造和管理以及靜態內容響應等職責。

下圖展示了API閘道器通常適應的架構:

API閘道器負責請求路由、組合以及協議轉換。所有來自客戶端的請求都先經過API閘道器,然後被路由分配到相應的微服務中,API閘道器通常呼叫多個微服務並聚合其結果來處理請求,它可以在HTTP或者WebSocket這些web友好協議與內部使用的web不友好協議間相互轉換。

API閘道器可以為每個客戶提供定製化的API,它通常為移動客戶端暴露粗粒度的API,比如提供(/productdetails?productid=xxx**)節點使得移動應用單一請求就能獲取所有的產品明細。API閘道器呼叫產品資訊、推薦、評分等服務,組合這些結果來處理客戶端請求。

一個非常牛的例子就是Netflix API閘道器,Netflix 流服務在上百種包含電視、機頂盒、智慧手機、遊戲系統、平板電腦等裝置上都可用。起初Netflix想為它們的流服務提供一種 one‑size‑fits‑all API,然而,他們發現由於裝置的不同劃分以及獨特需求,這樣設計是不現實的。現在他們使用API閘道器通過執行裝置相關的介面卡程式碼為客戶端提供裁剪的API,介面卡通常為每個請求呼叫平均六到七個後臺服務, Netflix API閘道器現在每天處理上億請求。

使用API閘道器的優勢與劣勢

正如你所想,使用API閘道器有優勢也有劣勢。一個巨大優勢就是它封裝了應用的內部結構,而不是讓客戶端直接呼叫每個服務,客戶端只需要簡單的與閘道器互動即可,另外API閘道器為不同客戶提供定製的API,並且減少了客戶端和應用間的網路呼叫,這也大大簡化了客戶端程式碼實現。

API閘道器也有一些劣勢,它本身是一個新的高可用的元件,需要被開發、部署和管理,同時API閘道器有可能成為開發的瓶頸。開發者為了暴露新的微服務節點必須更新API閘道器,把更新閘道器的流程做的儘量輕量級是很重要的,不然的話,開發者更新閘道器的時候就要被迫線上等待。儘管它有這些劣勢,在實戰中,應用使用API閘道器還是明智的選擇!

實現一個API閘道器

現在我們討論了API閘道器的動機和一些權衡,現在來考慮一些設計的問題吧:

效能與擴充套件性

只有少數類似Netflix的公司需要每天處理上億的請求,然而,對大多數應用來講,API閘道器的效能和擴充套件性通常也非常的重要。在一個支援非同步非阻塞IO的平臺上構建API閘道器是明智的選擇,我們有多種技術可以用來實現可擴充套件的API閘道器。基於JVM你可以選擇基於NIO的諸如Netty、Vertx、Spring Reactor或JBoss Undertow等框架,Node.js也是一個流行的選項,它是一個構建於Chrome JS引擎的平臺,另一選擇是使用NGINX Plus,它提供了成熟、可擴充套件、高效能的web伺服器和反向代理,並可以方便的被部署、配置和程式設計, NGINX Plus 可以管理使用者校驗、許可權控制、請求負載均衡、響應快取以及應用級別的健康檢查和監控。

使用響應式程式設計模型

API閘道器通過簡單路由到相應後臺服務來處理請求,通過呼叫多個後臺服務並聚合結果來處理它。對於一些請求,比如產品明細請求,後端對應的服務是彼此獨立的,為減少請求時間,API閘道器應該並行的處理這些請求。然而有時候,請求之間是有依賴關係的,API閘道器可能在路由請求到後臺服務之前先去呼叫使用者校驗服務驗證請求的合理性,類似的,在獲取使用者心願單中的上的產品資訊的時候,API閘道器必須先獲取包含那些資訊的使用者檔案再去獲取每個產品的資訊,另一個有趣的例子就是Netflix Video Grid

使用傳統的非同步回撥方式來寫API組合程式碼很快就會把你帶進回撥地獄。程式碼將會變得糾纏不清、難以理解也容易出錯。更好的方式是使用響應式方法來寫宣告式風格的API閘道器程式碼,比如,響應式抽象包括Scala中的 Future 、Java 8中的CompletableFuture 以及JavaScript中的Promise ,還有微軟為.NET開發的Reactive Extensions (also called Rx or ReactiveX), Netflix為了API閘道器的使用為基於JVM規範創造了RxJava,當然還有為JavaScript創造的RxJS ,可以執行在瀏覽器和Node.js中。使用響應式風格將會使你寫出更簡單更高效的API閘道器程式碼。

服務呼叫

微服務架構的應用是採用程序間通訊的分散式系統,存在兩種程序間通訊的方式:一種是採用非同步基於訊息機制的通訊,比如使用訊息中介產品 JMS 或者AMQP,當然還有 Zeromq服務直接呼叫的無中介訊息產品;另一種方式是使用HTTP或者Thrift這種同步機制進行通訊,一個系統應該同時使用同步和非同步風格,甚至為每種方式使用不同的實現,因此,API閘道器也必須支援這些不同的通訊機制。
 

服務發現

API閘道器需要知道和它通訊的每個服務的地址(IP地址和埠),在一個傳統應用中,你可能硬編碼,但在一個流行的,基於雲的微服務應用中,這就是一個大問題了。基礎架構服務,比如訊息中介,通常有一個靜態地址,我們可以在系統環境變數中之指定,然而,獲取一個應用服務的地址就不是一件簡單的事情了,應用服務擁有動態分配的地址,而且,一組服務例項可能因為自動擴充套件或升級而動態的變化,因此,API閘道器應該像系統中的其他服務客戶端一樣,需要服務發現機制:要麼是服務端發現 或者是 客戶端發現。稍後的文章將會詳細介紹服務發現的問題,現在,我們有必要意識到,如果系統使用客戶端服務發現的話,API閘道器應該能夠查詢服務註冊 Service Registry,服務註冊是所有服務例項登記其地址的資料庫。

處理區域性故障

實現API閘道器時需要強調的另一個問題是區域性故障。這個問題在分散式系統中很常見,比如一個服務可能呼叫另一個響應很慢或者不可用的服務,API閘道器千萬不要在等待已經掛掉服務響應的時候阻塞。當然,如何處理錯誤取決於具體的應用場景或者具體因為哪個服務掛掉:比如,如果產品明細場景中的推薦服務掛掉了,那麼API閘道器還是應該返回其他的產品資訊,保障產品對使用者仍然可以使用,推薦列表可以返回空或者預先硬編碼的Top 10商品,但是如果產品資訊服務掛掉的話,API閘道器就要返回客戶一個錯誤了。

API閘道器如果可能話也可以返回快取的資料,比如,由於產品價格很少變化,API閘道器可以在價格服務不可用時使用快取,資料可能是API閘道器自己快取,也可能快取在諸如Redis和Memcached這樣的外部快取中。通過返回預設值或者快取值,API閘道器確保區域性故障不會影響使用者體驗。

Netflix Hystrix在寫呼叫遠端服務程式碼時候是非常有用的,Hystrix 會標記超過特定閥值的呼叫為超時,它還實現了斷路器模式來阻止更多請求繼續呼叫沒有響應的服務,如果一個服務的出錯率超過了指定閥值,它會觸發斷路器,使得所有的請求快速失敗一段時間,Hystrix也允許你定義請求失敗時的fallback動作 ,比如讀取快取或者返回一個預設值。如果你使用JVM,那麼希望你一定考慮使用Hystrix,如果你不使用JVM,那也要有類似的工具來幫助你。

總結

對大多數基於微服務的應用來講,實現API閘道器是明智的,API閘道器就是一個應用的單一入口,它還負責路由請求、組合、協議轉換等工作,它為每個應用的客戶端提供定製化的API,它也可以通過返回預設值或快取值來處理失敗,下篇文章我們討論服務間的通訊問題。