在nginx官網(wǎng)的blog中,作者Chris Richardson關(guān)于微服務(wù)的文章有七篇:
1. Introduction to Microservices(微服務(wù)介紹)
2. Building Microservices: Using an API Gateway(構(gòu)建微服務(wù):API網(wǎng)關(guān))
3. Building Microservices: Inter-Process Communication in a Microservices Architecture(構(gòu)建微服務(wù):微服務(wù)架構(gòu)中的進(jìn)程間通信)
4. Service Discovery in a Microservices Architecture(微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn))
5. Event-Driven Data Management for Microservices(微服務(wù)中基于事件驅(qū)動的數(shù)據(jù)管理)
6. Choosing a Microservices Deployment Strategy(微服務(wù)部署策略)
7. Refactoring a Monolith into Microservices(重構(gòu)單體應(yīng)用為微服務(wù))
本篇文章翻譯的是其中的第二篇,關(guān)于微服務(wù)中的API網(wǎng)關(guān)。

00 寫在前面的話
當(dāng)你選擇使用一系列的微服務(wù)來構(gòu)建你的應(yīng)用的時候,你需要決定你的應(yīng)用如何與這些微服務(wù)進(jìn)行交互。在單體應(yīng)用中僅僅是一些端點(diǎn)(通常是重復(fù)的、負(fù)載均衡的),然而在微服務(wù)架構(gòu)中每個微服務(wù)都暴露一系列細(xì)粒度的端點(diǎn)。在這篇文章中,我們將研究這個問題對客戶端和應(yīng)用之間通信的影響,然后提出一個解決辦法,就是使用API網(wǎng)關(guān)。
01 簡介
假設(shè)你正在為購物應(yīng)用開發(fā)一個手機(jī)客戶端,好像你需要實(shí)現(xiàn)一個產(chǎn)品詳情頁,用來展示任何給定的產(chǎn)品的詳細(xì)信息。
舉個例子,下圖展示了你在Amazon的android手機(jī)客戶端上滾動看到的產(chǎn)品詳情頁。

即使這是一個智能手機(jī)上的應(yīng)用,產(chǎn)品詳情頁一樣展示了很多信息。例如,不僅有基本的產(chǎn)品信息(如名稱、描述和價格),而且這個頁面還展示了:
- 購物車中的商品數(shù)量
- 歷史訂單數(shù)
- 用戶評論
- 低庫存的預(yù)警
- 發(fā)貨選項(xiàng)
- 各種推薦,包括經(jīng)常和本產(chǎn)品一起被購買的其他產(chǎn)品,購買該產(chǎn)品的顧客購買的其他產(chǎn)品,還有購買該產(chǎn)品的顧客查看的其他產(chǎn)品
當(dāng)你使用單體應(yīng)用架構(gòu)時,手機(jī)客戶端可以簡單地通過一個REST接口(GET api.company.com/productdetails/productId)檢索到該數(shù)據(jù)。一個負(fù)載均衡器將請求路由到N個相同的應(yīng)用實(shí)例中的一個。然后這個應(yīng)用實(shí)例查詢多張數(shù)據(jù)庫表,最后將數(shù)據(jù)響應(yīng)給客戶端。
相反,當(dāng)我們使用微服務(wù)架構(gòu)之后,在產(chǎn)品詳情頁上展示的數(shù)據(jù)來自多個微服務(wù)。這里是一些可能擁有產(chǎn)品詳情頁數(shù)據(jù)的微服務(wù):
- 購物車服務(wù) - 購物車中物品的數(shù)量
- 訂單服務(wù) - 歷史訂單
- Catalog 服務(wù) - 產(chǎn)品基本信息,如名稱、圖片和價格
- 評論服務(wù) - 用戶的評論
- 庫存服務(wù) - 低庫存預(yù)警
- 郵寄服務(wù) - 發(fā)貨選項(xiàng),期限,來自各個郵寄提供商的API的成本
- 推薦服務(wù) - 推薦商品

我們需要決定手機(jī)客戶端如何訪問這些服務(wù),讓我們看看有幾種選擇。
02 客戶端和微服務(wù)直接交互
理論上來說,客戶端可以直接請求每個微服務(wù),每個微服務(wù)都有一個公共的端點(diǎn)(https://serviceName.api.company.name)。這個URL會被映射到微服務(wù)的負(fù)載均衡器上,然后它再分發(fā)請求到可用的應(yīng)用實(shí)例上。為了檢索產(chǎn)品詳情,客戶端需要向上面列出的所有微服務(wù)發(fā)送請求。
可惜,這種方法實(shí)現(xiàn)起來是有困難和限制的。一個問題是,客戶端需求和每個微服務(wù)暴露的細(xì)粒度的API是不匹配的。在這個例子中,客戶端需要分別發(fā)送七次請求,在更復(fù)雜的應(yīng)用中可能需要請求更多次。例如,Amazon介紹說,在他們的產(chǎn)品詳情頁上涉及到數(shù)百個微服務(wù)。雖然客戶端在局域網(wǎng)上可以發(fā)起這么多的請求,但是在公網(wǎng)上可能效率太低,這在移動網(wǎng)絡(luò)上是不符合實(shí)際的。而且這種方法也會使客戶端的代碼特別復(fù)雜。
客戶端直接調(diào)用微服務(wù)的另外一個問題是,一些微服務(wù)使用的協(xié)議不是web友好的。一個服務(wù)使用的是Thrift RPC的二進(jìn)制協(xié)議,而另一個服務(wù)可能使用的是AMQP消息協(xié)議,這兩個協(xié)議對瀏覽器和防火墻都是不友好的,最好是在內(nèi)部使用。一個應(yīng)用應(yīng)該使用例如 HTTP 和 WebSocket 這樣能穿透防火墻的協(xié)議。
這個方法的另外一個缺點(diǎn)是,它會使微服務(wù)的重構(gòu)比較苦難。隨著時間的推移,我們可能需要將某個系統(tǒng)拆分成服務(wù)。例如,我們可能將兩個服務(wù)合并成一個,或者將一個服務(wù)拆分成兩個甚至更多的服務(wù)。不管怎樣,如果客戶端和很多服務(wù)之間都是直接通信,這樣的重構(gòu)可能是極端困難。
由于以上這些問題,客戶端很少會和微服務(wù)直接通信。
03 API網(wǎng)關(guān)
通常情況下,更好的方法是使用所謂的API網(wǎng)關(guān)。API網(wǎng)關(guān)是一個系統(tǒng)的一個入口,它類似于面向?qū)ο笤O(shè)計(jì)中的外觀模式。API網(wǎng)關(guān)封裝了內(nèi)部系統(tǒng)架構(gòu),為每個客戶端單獨(dú)提供一個API。API網(wǎng)關(guān)可能還有其他的職責(zé),例如授權(quán)、監(jiān)控、負(fù)載均衡、緩存、請求的修改和管理、靜態(tài)響應(yīng)的處理。
下圖展示了一個API網(wǎng)關(guān)通常適合的架構(gòu):

API網(wǎng)關(guān)負(fù)責(zé)請求的路由、組合和協(xié)議轉(zhuǎn)換,所有來自客戶端的請求首先都要經(jīng)過API網(wǎng)關(guān),然后它再路由請求到合適的微服務(wù)。API網(wǎng)關(guān)處理請求的方式通常是,調(diào)用多個微服務(wù),然后合并響應(yīng)結(jié)果。API網(wǎng)關(guān)可以在web協(xié)議(如HTTP、WebSocket)和內(nèi)部使用的web不友好的協(xié)議之間做轉(zhuǎn)換。
API網(wǎng)關(guān)也可以為每個客戶端提供一個特定的API,它通常會為手機(jī)客戶端暴露一些粗粒度的API,例如在產(chǎn)品詳情的場景下,API網(wǎng)關(guān)可以提供一個端點(diǎn)(/productdetails?productid=xxx),這樣手機(jī)客戶端發(fā)送一個請求就能檢索到產(chǎn)品的所有信息。API網(wǎng)關(guān)處理這個請求調(diào)用了多個服務(wù)(產(chǎn)品信息、推薦、評論等),然后合并響應(yīng)結(jié)果。
Netflix API 網(wǎng)關(guān)是一個非常好的例子,Netflix的流服務(wù)支撐著數(shù)百種設(shè)備,包括電視、機(jī)頂盒、智能手機(jī)、游戲系統(tǒng)、平板電腦等等。最初,Netflix試圖給所有的設(shè)備提供統(tǒng)一的API服務(wù),然而他們發(fā)現(xiàn)效果不是很好,因?yàn)楦鞣N各樣的設(shè)備都有獨(dú)特的需求?,F(xiàn)在他們使用API網(wǎng)關(guān)為每種設(shè)備提供定制化的API,通過執(zhí)行特定設(shè)備的適配器代碼實(shí)現(xiàn)的。通常每個適配器處理一個請求平均要調(diào)用后端六七個服務(wù)。Netflix的API網(wǎng)關(guān)每天處理數(shù)十億的請求。
04 API網(wǎng)關(guān)的優(yōu)點(diǎn)和缺點(diǎn)
正如你期待的那樣,API網(wǎng)關(guān)既有優(yōu)點(diǎn)也有缺點(diǎn)。使用API網(wǎng)關(guān)的一個主要優(yōu)點(diǎn)就是,它封裝了應(yīng)用的內(nèi)部架構(gòu),客戶端不需要調(diào)用特定的服務(wù),而只需要與網(wǎng)關(guān)通信。API網(wǎng)關(guān)給每一種客戶端都提供了特定的API,這簡化了客戶端與應(yīng)用之間的通信往返次數(shù),也簡化了客戶端代碼。
API網(wǎng)關(guān)也有一些缺點(diǎn),這又是一個高可用的組件,需要開發(fā)、部署和管理。API網(wǎng)關(guān)成為開發(fā)的瓶頸,這也是有風(fēng)險(xiǎn)的。開發(fā)人員為了暴露每個微服務(wù)的端點(diǎn)必須更新API網(wǎng)關(guān),更新API網(wǎng)關(guān)的過程盡量輕量化也是很重要的,否則開發(fā)人員將在更新API網(wǎng)關(guān)的過程上被迫排隊(duì)。盡管有這么多的缺點(diǎn),但是在現(xiàn)實(shí)中的應(yīng)用上使用API網(wǎng)關(guān)還是有意義的。
05 API網(wǎng)關(guān)的實(shí)現(xiàn)
我們已經(jīng)看到了使用API網(wǎng)關(guān)的動機(jī)和一些利弊權(quán)衡,現(xiàn)在讓我們看一下你需要考慮的各種設(shè)計(jì)問題。
05.1 性能和伸縮性
只有少數(shù)的公司有Netflix的運(yùn)營規(guī)模,每天需要處理數(shù)十億的請求,然而對于大多數(shù)的應(yīng)用程序來說,API網(wǎng)關(guān)的性能和伸縮性是非常重要的。因此,在構(gòu)建API網(wǎng)關(guān)的時候使用異步調(diào)用和非阻塞I/O是非常有意義的。有很多種不同的技術(shù)都可以用來實(shí)現(xiàn)可伸縮的API網(wǎng)關(guān),在JVM平臺中你可以使用Netty、Vertx、Spring Reactor 或者 JBoss Undertow等這些機(jī)遇NIO的框架,在非JVM的平臺中流行的技術(shù)是Node.js,它是運(yùn)行在chrome的JavaScript引擎中的,另一個選擇是Nginx Plus,Nginx Plus提供了一個成熟的、可擴(kuò)展的、高性能的Web服務(wù)器,并且還提供了易于部署、配置和編程的反向代理,Nginx Plus可以管理授權(quán)、訪問控制、請求的負(fù)載均衡、響應(yīng)的緩存,并提供了應(yīng)用本身的健康檢查和監(jiān)控。
05.2 使用響應(yīng)式編程模型
API網(wǎng)關(guān)處理一些請求的方式是,簡單將他們路由到合適的后端服務(wù);處理其它請求的方式是,調(diào)用多個后端服務(wù),然后將它們的響應(yīng)結(jié)果聚合在一起。對于某些請求,如產(chǎn)品詳情的請求,它的后端服務(wù)都是彼此獨(dú)立的,為了減少響應(yīng)時間,API網(wǎng)關(guān)應(yīng)該并行地執(zhí)行這些獨(dú)立的請求。然而,有時候一些請求之間是彼此依賴的。API網(wǎng)關(guān)在將請求路由到后端服務(wù)之前,可能需要調(diào)用身份驗(yàn)證服務(wù)來驗(yàn)證請求。類似地,在獲取顧客需要的產(chǎn)品列表時,API網(wǎng)關(guān)必須首先檢索顧客需要的產(chǎn)品概要信息,然后才能檢索每個產(chǎn)品的詳細(xì)信息。另外一個有趣的API組合的示例是 Netflix Video Grid。
使用傳統(tǒng)的異步回調(diào)的方法編寫API組合的代碼,很快就會將你帶到回調(diào)的地獄,代碼將會變得混亂、難于理解并且易于出錯。一個更好的實(shí)現(xiàn)API網(wǎng)關(guān)的方法是,使用響應(yīng)式編程方法來實(shí)現(xiàn)聲明式的API網(wǎng)關(guān)代碼。響應(yīng)式概念的例子有Scala中的Future,Java 8 中的 CompletableFuture,JavaScript中的 Promise。也還有Reactive Extensions,也叫 Rx 或者 ReactiveX,最初是微軟為 .Net 平臺開發(fā)的;Netflix創(chuàng)建了JVM平臺的 RxJava,并將它使用在他們的API網(wǎng)關(guān)上;也有 JavaScript 上的 RxJS,它運(yùn)行在瀏覽器或者Node.js上。使用響應(yīng)式方法編寫API網(wǎng)關(guān)的代碼簡單又高效。
05.3 服務(wù)調(diào)用
基于微服務(wù)的應(yīng)用是一個分布式系統(tǒng),必須使用進(jìn)程間通信機(jī)制。進(jìn)程間通信有兩種方式:一種是異步的、基于消息的機(jī)制,有些是用消息中間件(JMS or AMQP)實(shí)現(xiàn)的,其它的是直接與服務(wù)通信的無中間件模式,如Zeromq;進(jìn)程間通信的另外一種方式是采用同步機(jī)制,如HTTP或者Thrift。通常一個系統(tǒng)會同時使用同步和異步方式,甚至?xí)褂妹糠N方式的不同實(shí)現(xiàn)形式,因此API網(wǎng)關(guān)必須支持多種通信機(jī)制。
05.4 服務(wù)發(fā)現(xiàn)
API網(wǎng)關(guān)需要知道要調(diào)用的每個微服務(wù)的位置(IP地址和端口號),在傳統(tǒng)的應(yīng)用中,你可能需要硬性地配置各個微服務(wù)的位置,但現(xiàn)在的基于云的微服務(wù)應(yīng)用中就很簡單了?;A(chǔ)設(shè)施服務(wù),例如消息中間件,通常都是一個靜態(tài)的位置,可以通過操作系統(tǒng)的環(huán)境變量來指定。然而,確定一個應(yīng)用服務(wù)的位置不是那么簡單的,應(yīng)用服務(wù)是動態(tài)分配位置的,并且一個服務(wù)的實(shí)例集合也是動態(tài)改變的,這是因?yàn)橐恍┳詣拥臄U(kuò)縮容和升級。因此,API網(wǎng)關(guān)要像其他的服務(wù)客戶端一樣,需要使用系統(tǒng)的服務(wù)發(fā)現(xiàn)機(jī)制:Server?Side Discovery 或者 Client?Side Discovery。在后面的文章中,將詳細(xì)地介紹服務(wù)發(fā)現(xiàn)?,F(xiàn)在需要我們注意的是,如果系統(tǒng)使用的是客戶端側(cè)的發(fā)現(xiàn),API網(wǎng)關(guān)必須能夠查詢到服務(wù)注冊中心,它是所有微服務(wù)實(shí)例和對應(yīng)位置的數(shù)據(jù)庫存儲。
05.5 部分失敗的處理
在實(shí)現(xiàn)API網(wǎng)關(guān)的時候必須解決的一個問題是部分失敗問題。這個問題在所有的分布式系統(tǒng)中都會出現(xiàn),因?yàn)橐粋€服務(wù)調(diào)用另外的服務(wù)時有可能響應(yīng)慢或者服務(wù)不可用。API網(wǎng)關(guān)決不能由于等待下游服務(wù)而被無限期的阻塞下去,例如在產(chǎn)品詳情的場景中,如果推薦服務(wù)未響應(yīng),API網(wǎng)關(guān)應(yīng)該將其余的產(chǎn)品詳情信息返回給客戶端,因?yàn)檫@些東西仍然對用戶是有用的,這時的推薦內(nèi)容是空的或者被其他的內(nèi)容代替,例如top 10的產(chǎn)品。但是如果產(chǎn)品信息服務(wù)未響應(yīng),API網(wǎng)關(guān)應(yīng)該返回錯誤給客戶端。
如果后端服務(wù)不可用,API網(wǎng)關(guān)也可以返回緩存數(shù)據(jù),例如,因?yàn)楫a(chǎn)品價格是很少改變的,如果產(chǎn)品價格服務(wù)不可用,API網(wǎng)關(guān)可以返回緩存的價格數(shù)據(jù)。數(shù)據(jù)可以被緩存在API網(wǎng)關(guān)本身,也可以緩存在外部,如Redis或者M(jìn)emcached。API網(wǎng)關(guān)在后端系統(tǒng)調(diào)用失敗的時候通過返回默認(rèn)數(shù)據(jù)和緩存數(shù)據(jù)來確保不影響用戶體驗(yàn)。
Netflix Hystrix是一個在調(diào)用遠(yuǎn)程服務(wù)時非常有用的編碼庫,Hystrix調(diào)用時間超過某個設(shè)定的閾值,就是所謂的超時,它是實(shí)現(xiàn)了斷路器模式的,這時它會阻止客戶端對不響應(yīng)的服務(wù)的不必要等待。如果一個服務(wù)的錯誤率超過指定的閾值,然后Hystrix就會觸發(fā)斷路器,然后所有的請求在一個時間區(qū)間內(nèi)都會立即失敗。Hystrix會讓你定義一個請求失敗后的返回函數(shù),例如從緩存讀取數(shù)據(jù)或者返回默認(rèn)值。如果你正在使用JVM的平臺,你應(yīng)該考慮使用Hystrix。如果在使用非JVM平臺你應(yīng)該等效的類庫。
06 總結(jié)
對于大多數(shù)基于微服務(wù)的應(yīng)用,實(shí)現(xiàn)一個API網(wǎng)關(guān)都是非常有意義的,它是一個系統(tǒng)的唯一入口。API網(wǎng)關(guān)負(fù)責(zé)請求路由、組合服務(wù)和協(xié)議轉(zhuǎn)換。它為應(yīng)用的每個客戶端提供定制化的API,API網(wǎng)關(guān)也可以通過返回緩存或默認(rèn)數(shù)據(jù)來屏蔽調(diào)用后端服務(wù)失敗。在這個系列的下一篇文章中,我們將介紹服務(wù)之間的通信。