原文鏈接:Building Microservices: Using an API Gateway
- 微服務(wù)介紹
- 構(gòu)建微服務(wù)之使用API網(wǎng)關(guān)(本文)
- 構(gòu)建微服務(wù)之:微服務(wù)架構(gòu)中的進程間通信
- 微服務(wù)中的服務(wù)發(fā)現(xiàn)
- 微服務(wù)之事件驅(qū)動的數(shù)據(jù)管理
- 選擇一種微服務(wù)部署策略
- 重構(gòu)單體應(yīng)用到微服務(wù)
對于設(shè)計、構(gòu)建和部署微服務(wù)系列七篇文章的第一篇,我們介紹了微服務(wù)架構(gòu)風(fēng)格,討論了微服務(wù)的優(yōu)勢和劣勢,盡管微服務(wù)有些復(fù)雜,但仍然是構(gòu)建復(fù)雜應(yīng)用的一個明智選擇,第二篇文章將討論使用API網(wǎng)關(guān)構(gòu)建微服務(wù)。
當(dāng)我們選擇把應(yīng)用構(gòu)建成一組微服務(wù)的時候,我們需要決定應(yīng)用的客戶端如何與這些微服務(wù)進行交互。傳統(tǒng)單體應(yīng)用中,往往只是一組(一般是replicated,負載均衡)的節(jié)點,而在微服務(wù)架構(gòu)中,每個微服務(wù)都會暴露一組細粒度的節(jié)點。這篇文章中,我們將檢驗這種方式如何影響客戶端與應(yīng)用端的通信,并且提出使用API網(wǎng)關(guān)的方式來解決這個問題。
介紹
假設(shè)我們正在為一個商品應(yīng)用開發(fā)一個原生移動客戶端,我們應(yīng)該提供一個產(chǎn)品明細頁來展示指定產(chǎn)品的信息。
正如下圖所示,當(dāng)我們在亞馬遜的安卓移動應(yīng)用中滾動產(chǎn)品明細頁時,它將會呈現(xiàn)給我們:

盡管這是移動應(yīng)用,產(chǎn)品明細頁依然展示給我們很多信息,比如它不僅僅展示了產(chǎn)品的基本信息(比如名稱、描述、價格等),還展示了:
- 購物車中的條目數(shù)
- 訂單歷史記錄
- 用戶點評
- 低庫存預(yù)警
- 配送選項
- 各項推薦,包括購買本產(chǎn)品還經(jīng)常一起購買了其它某產(chǎn)品,客戶買了這個產(chǎn)品同時還買了其他某產(chǎn)品,購買該產(chǎn)品的用戶還瀏覽了哪些產(chǎn)品
- 替代購買選項
當(dāng)我們使用單體架構(gòu)模式的時候,一個移動客戶端可能通過發(fā)送單一的REST調(diào)用請求(GET api.company.com/productdetails/productId) 來獲取展示的數(shù)據(jù),負載均衡器會把該請求路由到多個相同應(yīng)用實例的其中一臺,應(yīng)用繼續(xù)查詢不同的數(shù)據(jù)庫表并返回請求數(shù)據(jù)給客戶端。
對應(yīng)的,在使用微服務(wù)架構(gòu)模式的時候,需要在產(chǎn)品明細頁展示的數(shù)據(jù)被多個微服務(wù)所擁有,下面是一些可能擁有需要展示數(shù)據(jù)在產(chǎn)品明細頁的微服務(wù):
- 購物車服務(wù):購物車中的產(chǎn)品條目
- 訂單服務(wù):訂單歷史
- 目錄服務(wù):基本產(chǎn)品信息,比如名稱、圖片、價格等
- 點評服務(wù):用戶點評
- 庫存服務(wù):低庫存預(yù)警
- 配送服務(wù):配送選項、時限以及來自配送提供者API計算出的費用
-
推薦服務(wù):建議購買項
Paste_Image.png
我們需要決定移動端如何訪問這些服務(wù),先看下面的選項:
客戶端直接與微服務(wù)通信
理論上客戶端可以直接與每一個微服務(wù)進行通信,每個微服務(wù)將會有一個公開的節(jié)點(https://serviceName.api.company.name**)),這個URL將會映射到負載均衡器,然后被分發(fā)到可用的實例上被處理,為了獲取產(chǎn)品明細,移動客戶端需要向上面列出的各個微服務(wù)發(fā)送請求。
非常不幸的是,這種方案有諸多挑戰(zhàn)和限制,問題之一就是客戶端與每個微服務(wù)暴露出的細粒度API之間的不匹配,本例子中的客戶端需發(fā)送七個不同的請求,在一個更加復(fù)雜的應(yīng)用中請求數(shù)可能更多,比如亞馬遜在渲染產(chǎn)品頁的時候可能要調(diào)用上百個服務(wù)來渲染頁面,一個客戶端可以在LAN中發(fā)送多個請求,但是在公網(wǎng)上就特別低效,那就不用提在移動設(shè)備上了,當(dāng)然,這種方式也使得客戶端異常的復(fù)雜。
客戶端直接調(diào)用微服務(wù)的另一個問題是,一些服務(wù)可能使用對web并不友好的協(xié)議實現(xiàn)。一個服務(wù)可能使用Thrift二進制的RPC而另一個服務(wù)可能使用AMQP消息協(xié)議。這些協(xié)議都不是瀏覽器和防火墻友好的,最好是在應(yīng)用內(nèi)部被使用。防火墻之外呢,應(yīng)用最好使用HTTP或者WebSocket。
這種方式另一個劣勢是使得微服務(wù)重構(gòu)變得困難,隨著時間推移,我們可能需要重新劃分、組織微服務(wù),比如我們可能合并兩個微服務(wù),也可能把某微服務(wù)拆分為兩個或多個,如果客戶端直接與微服務(wù)交互的話,對這些微服務(wù)進行重構(gòu)變得異常困難。
正是由于這些問題,采用客戶端直接調(diào)用微服務(wù)的方式并不明智。
使用API網(wǎng)關(guān)
通常更好的方式是使用大家都熟知的API網(wǎng)關(guān),API網(wǎng)關(guān)是提供系統(tǒng)唯一入口的一臺服務(wù)器,它和面向?qū)ο笤O(shè)計模式中的門面類似:API網(wǎng)關(guān)封裝了內(nèi)部的系統(tǒng)架構(gòu)并向每個客戶端提供裁剪的API,它也可能負責(zé)諸如用戶驗證、監(jiān)控、負載均衡、緩存、請求改造和管理以及靜態(tài)內(nèi)容響應(yīng)等職責(zé)。
下圖展示了API網(wǎng)關(guān)通常適應(yīng)的架構(gòu):

API網(wǎng)關(guān)負責(zé)請求路由、組合以及協(xié)議轉(zhuǎn)換。所有來自客戶端的請求都先經(jīng)過API網(wǎng)關(guān),然后被路由分配到相應(yīng)的微服務(wù)中,API網(wǎng)關(guān)通常調(diào)用多個微服務(wù)并聚合其結(jié)果來處理請求,它可以在HTTP或者WebSocket這些web友好協(xié)議與內(nèi)部使用的web不友好協(xié)議間相互轉(zhuǎn)換。
API網(wǎng)關(guān)可以為每個客戶提供定制化的API,它通常為移動客戶端暴露粗粒度的API,比如提供(/productdetails?productid=xxx**)節(jié)點使得移動應(yīng)用單一請求就能獲取所有的產(chǎn)品明細。API網(wǎng)關(guān)調(diào)用產(chǎn)品信息、推薦、評分等服務(wù),組合這些結(jié)果來處理客戶端請求。
一個非常牛的例子就是Netflix API網(wǎng)關(guān),Netflix 流服務(wù)在上百種包含電視、機頂盒、智能手機、游戲系統(tǒng)、平板電腦等設(shè)備上都可用。起初Netflix想為它們的流服務(wù)提供一種 one?size?fits?all API,然而,他們發(fā)現(xiàn)由于設(shè)備的不同劃分以及獨特需求,這樣設(shè)計是不現(xiàn)實的?,F(xiàn)在他們使用API網(wǎng)關(guān)通過運行設(shè)備相關(guān)的適配器代碼為客戶端提供裁剪的API,適配器通常為每個請求調(diào)用平均六到七個后臺服務(wù), Netflix API網(wǎng)關(guān)現(xiàn)在每天處理上億請求。
使用API網(wǎng)關(guān)的優(yōu)勢與劣勢
正如你所想,使用API網(wǎng)關(guān)有優(yōu)勢也有劣勢。一個巨大優(yōu)勢就是它封裝了應(yīng)用的內(nèi)部結(jié)構(gòu),而不是讓客戶端直接調(diào)用每個服務(wù),客戶端只需要簡單的與網(wǎng)關(guān)交互即可,另外API網(wǎng)關(guān)為不同客戶提供定制的API,并且減少了客戶端和應(yīng)用間的網(wǎng)絡(luò)調(diào)用,這也大大簡化了客戶端代碼實現(xiàn)。
API網(wǎng)關(guān)也有一些劣勢,它本身是一個新的高可用的組件,需要被開發(fā)、部署和管理,同時API網(wǎng)關(guān)有可能成為開發(fā)的瓶頸。開發(fā)者為了暴露新的微服務(wù)節(jié)點必須更新API網(wǎng)關(guān),把更新網(wǎng)關(guān)的流程做的盡量輕量級是很重要的,不然的話,開發(fā)者更新網(wǎng)關(guān)的時候就要被迫在線等待。盡管它有這些劣勢,在實戰(zhàn)中,應(yīng)用使用API網(wǎng)關(guān)還是明智的選擇!
實現(xiàn)一個API網(wǎng)關(guān)
現(xiàn)在我們討論了API網(wǎng)關(guān)的動機和一些權(quán)衡,現(xiàn)在來考慮一些設(shè)計的問題吧:
性能與擴展性
只有少數(shù)類似Netflix的公司需要每天處理上億的請求,然而,對大多數(shù)應(yīng)用來講,API網(wǎng)關(guān)的性能和擴展性通常也非常的重要。在一個支持異步非阻塞IO的平臺上構(gòu)建API網(wǎng)關(guān)是明智的選擇,我們有多種技術(shù)可以用來實現(xiàn)可擴展的API網(wǎng)關(guān)?;贘VM你可以選擇基于NIO的諸如Netty、Vertx、Spring Reactor或JBoss Undertow等框架,Node.js也是一個流行的選項,它是一個構(gòu)建于Chrome JS引擎的平臺,另一選擇是使用NGINX Plus,它提供了成熟、可擴展、高性能的web服務(wù)器和反向代理,并可以方便的被部署、配置和編程, NGINX Plus 可以管理用戶校驗、權(quán)限控制、請求負載均衡、響應(yīng)緩存以及應(yīng)用級別的健康檢查和監(jiān)控。
使用響應(yīng)式編程模型
API網(wǎng)關(guān)通過簡單路由到相應(yīng)后臺服務(wù)來處理請求,通過調(diào)用多個后臺服務(wù)并聚合結(jié)果來處理它。對于一些請求,比如產(chǎn)品明細請求,后端對應(yīng)的服務(wù)是彼此獨立的,為減少請求時間,API網(wǎng)關(guān)應(yīng)該并行的處理這些請求。然而有時候,請求之間是有依賴關(guān)系的,API網(wǎng)關(guān)可能在路由請求到后臺服務(wù)之前先去調(diào)用用戶校驗服務(wù)驗證請求的合理性,類似的,在獲取用戶心愿單中的上的產(chǎn)品信息的時候,API網(wǎng)關(guān)必須先獲取包含那些信息的用戶檔案再去獲取每個產(chǎn)品的信息,另一個有趣的例子就是Netflix Video Grid。
使用傳統(tǒng)的異步回調(diào)方式來寫API組合代碼很快就會把你帶進回調(diào)地獄。代碼將會變得糾纏不清、難以理解也容易出錯。更好的方式是使用響應(yīng)式方法來寫聲明式風(fēng)格的API網(wǎng)關(guān)代碼,比如,響應(yīng)式抽象包括Scala中的 Future 、Java 8中的CompletableFuture 以及JavaScript中的Promise ,還有微軟為.NET開發(fā)的Reactive Extensions (also called Rx or ReactiveX), Netflix為了API網(wǎng)關(guān)的使用為基于JVM規(guī)范創(chuàng)造了RxJava,當(dāng)然還有為JavaScript創(chuàng)造的RxJS ,可以運行在瀏覽器和Node.js中。使用響應(yīng)式風(fēng)格將會使你寫出更簡單更高效的API網(wǎng)關(guān)代碼。
服務(wù)調(diào)用
微服務(wù)架構(gòu)的應(yīng)用是采用進程間通信的分布式系統(tǒng),存在兩種進程間通訊的方式:一種是采用異步基于消息機制的通信,比如使用消息中介產(chǎn)品 JMS 或者AMQP,當(dāng)然還有 Zeromq服務(wù)直接調(diào)用的無中介消息產(chǎn)品;另一種方式是使用HTTP或者Thrift這種同步機制進行通信,一個系統(tǒng)應(yīng)該同時使用同步和異步風(fēng)格,甚至為每種方式使用不同的實現(xiàn),因此,API網(wǎng)關(guān)也必須支持這些不同的通信機制。
服務(wù)發(fā)現(xiàn)
API網(wǎng)關(guān)需要知道和它通信的每個服務(wù)的地址(IP地址和端口),在一個傳統(tǒng)應(yīng)用中,你可能硬編碼,但在一個流行的,基于云的微服務(wù)應(yīng)用中,這就是一個大問題了?;A(chǔ)架構(gòu)服務(wù),比如消息中介,通常有一個靜態(tài)地址,我們可以在系統(tǒng)環(huán)境變量中之指定,然而,獲取一個應(yīng)用服務(wù)的地址就不是一件簡單的事情了,應(yīng)用服務(wù)擁有動態(tài)分配的地址,而且,一組服務(wù)實例可能因為自動擴展或升級而動態(tài)的變化,因此,API網(wǎng)關(guān)應(yīng)該像系統(tǒng)中的其他服務(wù)客戶端一樣,需要服務(wù)發(fā)現(xiàn)機制:要么是服務(wù)端發(fā)現(xiàn) 或者是 客戶端發(fā)現(xiàn)。稍后的文章將會詳細介紹服務(wù)發(fā)現(xiàn)的問題,現(xiàn)在,我們有必要意識到,如果系統(tǒng)使用客戶端服務(wù)發(fā)現(xiàn)的話,API網(wǎng)關(guān)應(yīng)該能夠查詢服務(wù)注冊 Service Registry,服務(wù)注冊是所有服務(wù)實例登記其地址的數(shù)據(jù)庫。
處理局部故障
實現(xiàn)API網(wǎng)關(guān)時需要強調(diào)的另一個問題是局部故障。這個問題在分布式系統(tǒng)中很常見,比如一個服務(wù)可能調(diào)用另一個響應(yīng)很慢或者不可用的服務(wù),API網(wǎng)關(guān)千萬不要在等待已經(jīng)掛掉服務(wù)響應(yīng)的時候阻塞。當(dāng)然,如何處理錯誤取決于具體的應(yīng)用場景或者具體因為哪個服務(wù)掛掉:比如,如果產(chǎn)品明細場景中的推薦服務(wù)掛掉了,那么API網(wǎng)關(guān)還是應(yīng)該返回其他的產(chǎn)品信息,保障產(chǎn)品對用戶仍然可以使用,推薦列表可以返回空或者預(yù)先硬編碼的Top 10商品,但是如果產(chǎn)品信息服務(wù)掛掉的話,API網(wǎng)關(guān)就要返回客戶一個錯誤了。
API網(wǎng)關(guān)如果可能話也可以返回緩存的數(shù)據(jù),比如,由于產(chǎn)品價格很少變化,API網(wǎng)關(guān)可以在價格服務(wù)不可用時使用緩存,數(shù)據(jù)可能是API網(wǎng)關(guān)自己緩存,也可能緩存在諸如Redis和Memcached這樣的外部緩存中。通過返回默認值或者緩存值,API網(wǎng)關(guān)確保局部故障不會影響用戶體驗。
Netflix Hystrix在寫調(diào)用遠端服務(wù)代碼時候是非常有用的,Hystrix 會標(biāo)記超過特定閥值的調(diào)用為超時,它還實現(xiàn)了斷路器模式來阻止更多請求繼續(xù)調(diào)用沒有響應(yīng)的服務(wù),如果一個服務(wù)的出錯率超過了指定閥值,它會觸發(fā)斷路器,使得所有的請求快速失敗一段時間,Hystrix也允許你定義請求失敗時的fallback動作 ,比如讀取緩存或者返回一個默認值。如果你使用JVM,那么希望你一定考慮使用Hystrix,如果你不使用JVM,那也要有類似的工具來幫助你。
總結(jié)
對大多數(shù)基于微服務(wù)的應(yīng)用來講,實現(xiàn)API網(wǎng)關(guān)是明智的,API網(wǎng)關(guān)就是一個應(yīng)用的單一入口,它還負責(zé)路由請求、組合、協(xié)議轉(zhuǎn)換等工作,它為每個應(yīng)用的客戶端提供定制化的API,它也可以通過返回默認值或緩存值來處理失敗,下篇文章我們討論服務(wù)間的通信問題。
