原文鏈接:Building Microservices: Using an API Gateway
- 微服務介紹
- 構建微服務之使用API網關(本文)
- 構建微服務之:微服務架構中的進程間通信
- 微服務中的服務發現
- 微服務之事件驅動的數據管理
- 選擇一種微服務部署策略
- 重構單體應用到微服務
對于設計、構建和部署微服務系列七篇文章的第一篇,我們介紹了微服務架構風格,討論了微服務的優勢和劣勢,盡管微服務有些復雜,但仍然是構建復雜應用的一個明智選擇,第二篇文章將討論使用API網關構建微服務。
當我們選擇把應用構建成一組微服務的時候,我們需要決定應用的客戶端如何與這些微服務進行交互。傳統單體應用中,往往只是一組(一般是replicated,負載均衡)的節點,而在微服務架構中,每個微服務都會暴露一組細粒度的節點。這篇文章中,我們將檢驗這種方式如何影響客戶端與應用端的通信,并且提出使用API網關的方式來解決這個問題。
介紹
假設我們正在為一個商品應用開發一個原生移動客戶端,我們應該提供一個產品明細頁來展示指定產品的信息。
正如下圖所示,當我們在亞馬遜的安卓移動應用中滾動產品明細頁時,它將會呈現給我們:
盡管這是移動應用,產品明細頁依然展示給我們很多信息,比如它不僅僅展示了產品的基本信息(比如名稱、描述、價格等),還展示了:
- 購物車中的條目數
- 訂單歷史記錄
- 用戶點評
- 低庫存預警
- 配送選項
- 各項推薦,包括購買本產品還經常一起購買了其它某產品,客戶買了這個產品同時還買了其他某產品,購買該產品的用戶還瀏覽了哪些產品
- 替代購買選項
當我們使用單體架構模式的時候,一個移動客戶端可能通過發送單一的REST調用請求(GET api.company.com/productdetails/productId) 來獲取展示的數據,負載均衡器會把該請求路由到多個相同應用實例的其中一臺,應用繼續查詢不同的數據庫表并返回請求數據給客戶端。
對應的,在使用微服務架構模式的時候,需要在產品明細頁展示的數據被多個微服務所擁有,下面是一些可能擁有需要展示數據在產品明細頁的微服務:
- 購物車服務:購物車中的產品條目
- 訂單服務:訂單歷史
- 目錄服務:基本產品信息,比如名稱、圖片、價格等
- 點評服務:用戶點評
- 庫存服務:低庫存預警
- 配送服務:配送選項、時限以及來自配送提供者API計算出的費用
-
推薦服務:建議購買項
Paste_Image.png
我們需要決定移動端如何訪問這些服務,先看下面的選項:
客戶端直接與微服務通信
理論上客戶端可以直接與每一個微服務進行通信,每個微服務將會有一個公開的節點(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,它也可以通過返回默認值或緩存值來處理失敗,下篇文章我們討論服務間的通信問題。