編者的話 |本文來(lái)自 Nginx 官方博客,是微服務(wù)系列文章的第二篇,本文將探討:微服務(wù)架構(gòu)是如何影響客戶端到服務(wù)端的通信,并提出一種使用 API 網(wǎng)關(guān)的方法。
作者介紹:Chris Richardson,是世界著名的軟件大師,經(jīng)典技術(shù)著作《POJOS IN ACTION》一書(shū)的作者,也是 cloudfoundry.com 最初的創(chuàng)始人,Chris Richardson 與 Martin Fowler、Sam Newman、Adrian Cockcroft 等并稱為世界十大軟件架構(gòu)師。
Chris Richardson 微服務(wù)系列全 7 篇:
1. 微服務(wù)架構(gòu)概念解析
2. 構(gòu)建微服務(wù)架構(gòu):使用 API Gateway(本篇文章)
3. 深入微服務(wù)架構(gòu)的進(jìn)程間通信
4. 服務(wù)發(fā)現(xiàn)的可行方案以及實(shí)踐案例
5. 微服務(wù)的事件驅(qū)動(dòng)數(shù)據(jù)管理
6. 選擇微服務(wù)部署策略
7. 將單體應(yīng)用改造為微服務(wù)
簡(jiǎn)要概述
讓我們想象一下,你要為一個(gè)購(gòu)物應(yīng)用程序開(kāi)發(fā)一個(gè)原生移動(dòng)客戶端。你很可能需要實(shí)現(xiàn)一個(gè)產(chǎn)品詳情頁(yè)面,展示任何指定商品的信息。
下圖展示了 Amazon Android 應(yīng)用在商品詳情頁(yè)顯示的內(nèi)容。
即使只是個(gè)智能手機(jī)應(yīng)用,產(chǎn)品詳情頁(yè)面也顯示了大量的信息。該頁(yè)面不僅包含基本的產(chǎn)品信息(如名稱、描述、價(jià)格),而且還顯示了如下內(nèi)容:
- 購(gòu)物車中的商品數(shù)目
- 歷史訂單
- 客戶評(píng)論
- 低庫(kù)存預(yù)警
- 送貨選項(xiàng)
- 各種推薦,包括經(jīng)常與該商品一起購(gòu)買的其他商品、購(gòu)買該商品的客戶購(gòu)買的其他商品,購(gòu)買該商品的客戶看過(guò)的其他商品
- 其他購(gòu)物選項(xiàng)
使用單體應(yīng)用程序架構(gòu)時(shí),移動(dòng)客戶端通過(guò)向應(yīng)用程序發(fā)起一次 REST 調(diào)用(GET api.company.com/productdetails/
)來(lái)獲取這些數(shù)據(jù)。負(fù)載均衡器將請(qǐng)求路由給 N 個(gè)相同的應(yīng)用程序?qū)嵗械钠渲兄弧H缓螅瑧?yīng)用程序會(huì)查詢各種數(shù)據(jù)庫(kù)表,并將響應(yīng)返回給客戶端。
相反,若是采用微服務(wù)架構(gòu),顯示在產(chǎn)品頁(yè)上的數(shù)據(jù)會(huì)分布在不同的微服務(wù)上。下面列舉了可能與產(chǎn)品詳情頁(yè)數(shù)據(jù)有關(guān)的一些微服務(wù):
- 購(gòu)物車服務(wù)網(wǎng)——購(gòu)物車中的件數(shù)
- 訂單服務(wù)——訂單歷史
- 目錄服務(wù)——商品基本信息,如名稱、圖片和價(jià)格
- 評(píng)論服務(wù)——客戶的評(píng)論
- 庫(kù)存服務(wù)——低庫(kù)存預(yù)警
- 送貨服務(wù)——送貨選項(xiàng)、期限和費(fèi)用,這些信息單獨(dú)從送貨方API獲取
- 推薦服務(wù)——推薦商品
我們需要決定移動(dòng)客戶端如何訪問(wèn)這些服務(wù)。讓我們看看有哪些方法。
客戶端與微服務(wù)直接通信
從理論上講,客戶端可以直接向每個(gè)微服務(wù)發(fā)送請(qǐng)求。每個(gè)微服務(wù)都有一個(gè)公開(kāi)的端點(diǎn)(https ://.api.company.name)。該 URL 映射到微服務(wù)的負(fù)載均衡器,由后者負(fù)責(zé)在可用實(shí)例之間分發(fā)請(qǐng)求。為了獲取產(chǎn)品詳情,移動(dòng)客戶端將逐一向上文列出的 N 個(gè)服務(wù)發(fā)送請(qǐng)求。
遺憾的是,這種方法存在挑戰(zhàn)和局限。問(wèn)題之一是客戶端需求和每個(gè)微服務(wù)暴露的細(xì)粒度 API 不匹配。在這個(gè)例子中,客戶端需要發(fā)送 7 個(gè)獨(dú)立請(qǐng)求。在更復(fù)雜的應(yīng)用程序中,可能要發(fā)送更多的請(qǐng)求;按照 Amazon 的說(shuō)法,他們?cè)陲@示他們的產(chǎn)品頁(yè)面時(shí)就調(diào)用了數(shù)百個(gè)服務(wù)。然而,客戶端通過(guò) LAN 發(fā)送許多請(qǐng)求,這在公網(wǎng)上可能會(huì)很低效,在移動(dòng)網(wǎng)絡(luò)上就根本不可行。這種方法還使得客戶端代碼非常復(fù)雜。
客戶端直接調(diào)用微服務(wù)的另一個(gè)問(wèn)題是,部分服務(wù)使用的協(xié)議對(duì) web 并不友好。一個(gè)服務(wù)可能使用 Thrift 二進(jìn)制 RPC,而另一個(gè)服務(wù)可能使用 AMQP 消息傳遞協(xié)議。不管哪種協(xié)議對(duì)于瀏覽器或防火墻都不夠友好,最好是內(nèi)部使用。在防火墻之外,應(yīng)用程序應(yīng)該使用諸如 HTTP 和 WebSocket 之類的協(xié)議。
這種方法的另一個(gè)缺點(diǎn)是,它會(huì)使得微服務(wù)難以重構(gòu)。隨著時(shí)間推移,我們可能想要更改系統(tǒng)拆分成服務(wù)的方式。例如,我們可能合并兩個(gè)服務(wù),或者將一個(gè)服務(wù)拆分成兩個(gè)或更多服務(wù)。然而,如果客戶端與微服務(wù)直接通信,那么執(zhí)行這類重構(gòu)就非常困難了。
由于上述三種問(wèn)題的原因,客戶端直接與服務(wù)器端通信的方式很少在實(shí)際中使用。
使用API網(wǎng)關(guān)構(gòu)建微服務(wù)
通常來(lái)說(shuō),使用 API 網(wǎng)關(guān)是更好的解決方式。API 網(wǎng)關(guān)是一個(gè)服務(wù)器,也可以說(shuō)是進(jìn)入系統(tǒng)的唯一節(jié)點(diǎn)。這與面向?qū)ο笤O(shè)計(jì)模式中的 Facade 模式很像。API 網(wǎng)關(guān)封裝內(nèi)部系統(tǒng)的架構(gòu),并且提供 API 給各個(gè)客戶端。它還可能還具備授權(quán)、監(jiān)控、負(fù)載均衡、緩存、請(qǐng)求分片和管理、靜態(tài)響應(yīng)處理等功能。下圖展示了一個(gè)適應(yīng)當(dāng)前架構(gòu)的 API 網(wǎng)關(guān)。
API 網(wǎng)關(guān)負(fù)責(zé)服務(wù)請(qǐng)求路由、組合及協(xié)議轉(zhuǎn)換。客戶端的所有請(qǐng)求都首先經(jīng)過(guò) API 網(wǎng)關(guān),然后由它將請(qǐng)求路由到合適的微服務(wù)。API 網(wǎng)關(guān)經(jīng)常會(huì)通過(guò)調(diào)用多個(gè)微服務(wù)并合并結(jié)果來(lái)處理一個(gè)請(qǐng)求。它可以在 web 協(xié)議(如 HTTP 與 WebSocket)與內(nèi)部使用的非 web 友好協(xié)議之間轉(zhuǎn)換。
API 網(wǎng)關(guān)還能為每個(gè)客戶端提供一個(gè)定制的 API。通常,它會(huì)向移動(dòng)客戶端暴露一個(gè)粗粒度的 API。以產(chǎn)品詳情的場(chǎng)景為例,API 網(wǎng)關(guān)可以提供一個(gè)端點(diǎn)(/productdetails?productid=xxx),使移動(dòng)客戶端可以通過(guò)一個(gè)請(qǐng)求獲取所有的產(chǎn)品詳情。API 網(wǎng)關(guān)通過(guò)調(diào)用各個(gè)服務(wù)(產(chǎn)品信息、推薦、評(píng)論等等)并合并結(jié)果來(lái)處理請(qǐng)求。
Netflix API 網(wǎng)關(guān)是一個(gè)很好的 API 網(wǎng)關(guān)實(shí)例。Netflix 流媒體服務(wù)提供給成百上千種類型的設(shè)備使用,包括電視、機(jī)頂盒、智能手機(jī)、游戲系統(tǒng)、平板電腦等等。
最初,Netflix 試圖為他們的流媒體服務(wù)提供一個(gè)通用的 API。然而他們發(fā)現(xiàn),由于各種各樣的設(shè)備都有自己獨(dú)特的需求,這種方式并不能很好地工作。如今,他們使用一個(gè) API 網(wǎng)關(guān),通過(guò)運(yùn)行與針對(duì)特定設(shè)備的適配器代碼,來(lái)為每種設(shè)備提供定制的 API。通常,一個(gè)適配器通過(guò)調(diào)用平均 6 到 7 個(gè)后端服務(wù)來(lái)處理每個(gè)請(qǐng)求。Netflix API 網(wǎng)關(guān)每天處理數(shù)十億請(qǐng)求。
API網(wǎng)關(guān)的優(yōu)點(diǎn)和缺點(diǎn)
如你所料,使用 API 網(wǎng)關(guān)有優(yōu)點(diǎn)也有不足。使用 API 網(wǎng)關(guān)的最大優(yōu)點(diǎn)是,它封裝了應(yīng)用程序的內(nèi)部結(jié)構(gòu)。客戶端只需要同網(wǎng)關(guān)交互,而不必調(diào)用特定的服務(wù)。API 網(wǎng)關(guān)為每一類客戶端提供了特定的 API,這減少了客戶端與應(yīng)用程序間的交互次數(shù),還簡(jiǎn)化了客戶端代碼。
API 網(wǎng)關(guān)也有一些不足。它增加了一個(gè)我們必須開(kāi)發(fā)、部署和維護(hù)的高可用組件。還有一個(gè)風(fēng)險(xiǎn)是,API 網(wǎng)關(guān)變成了開(kāi)發(fā)瓶頸。為了暴露每個(gè)微服務(wù)的端點(diǎn),開(kāi)發(fā)人員必須更新 API 網(wǎng)關(guān)。API網(wǎng)關(guān)的更新過(guò)程要盡可能地簡(jiǎn)單,這很重要;否則,為了更新網(wǎng)關(guān),開(kāi)發(fā)人員將不得不排隊(duì)等待。不過(guò),雖然有這些不足,但對(duì)于大多數(shù)現(xiàn)實(shí)世界的應(yīng)用程序而言,使用 API 網(wǎng)關(guān)是合理的。
實(shí)現(xiàn)API網(wǎng)關(guān)
到目前為止,我們已經(jīng)探討了使用 API 網(wǎng)關(guān)的動(dòng)力及其優(yōu)缺點(diǎn)。下面讓我們看一下需要考慮的各種設(shè)計(jì)問(wèn)題。
性能和可擴(kuò)展性
只有少數(shù)公司擁有 Netflix 這樣的規(guī)模,需要每天處理每天需要處理數(shù)十億請(qǐng)求。不管怎樣,對(duì)于大多數(shù)應(yīng)用程序而言,API 網(wǎng)關(guān)的性能和可擴(kuò)展性都非常重要。因此,將 API 網(wǎng)關(guān)構(gòu)建在一個(gè)支持異步、I/O 非阻塞的平臺(tái)上是合理的。有多種不同的技術(shù)可以實(shí)現(xiàn)一個(gè)可擴(kuò)展的 API 網(wǎng)關(guān)。在 JVM 上,可以使用一種基于 NIO 的框架,比如 Netty、Vertx、Spring Reactor 或 JBoss Undertow 中的一種。一個(gè)非常流行的非 JVM 選項(xiàng)是 Node.js,它是一個(gè)基于 Chrome JavaScript 引擎構(gòu)建的平臺(tái)。
另一個(gè)方法是使用 NGINX Plus。NGINX Plus 提供了一個(gè)成熟的、可擴(kuò)展的、高性能 web 服務(wù)器和一個(gè)易于部署的、可配置可編程的反向代理。NGINX Plus 可以管理身份驗(yàn)證、訪問(wèn)控制、負(fù)載均衡請(qǐng)求、緩存響應(yīng),并提供應(yīng)用程序可感知的健康檢查和監(jiān)控。
使用響應(yīng)式編程模型
API 網(wǎng)關(guān)通過(guò)簡(jiǎn)單地將請(qǐng)求路由給合適的后端服務(wù)來(lái)處理部分請(qǐng)求,而通過(guò)調(diào)用多個(gè)后端服務(wù)并合并結(jié)果來(lái)處理其它請(qǐng)求。對(duì)于部分請(qǐng)求,比如產(chǎn)品詳情相關(guān)的多個(gè)請(qǐng)求,它們對(duì)后端服務(wù)的請(qǐng)求是獨(dú)立于其它請(qǐng)求的。為了最小化響應(yīng)時(shí)間,API 網(wǎng)關(guān)應(yīng)該并發(fā)執(zhí)行獨(dú)立請(qǐng)求。
然而,有時(shí)候,請(qǐng)求之間存在依賴。在將請(qǐng)求路由到后端服務(wù)之前,API 網(wǎng)關(guān)可能首先需要調(diào)用身份驗(yàn)證服務(wù)驗(yàn)證請(qǐng)求的合法性。類似地,為了獲取客戶心愿單中的產(chǎn)品信息,API 網(wǎng)關(guān)必須首先獲取包含這些信息的客戶資料,然后再獲取每個(gè)產(chǎn)品的信息。關(guān)于 API 組合,另一個(gè)有趣的例子是 Netflix Video Grid。
使用傳統(tǒng)的異步回調(diào)方法編寫 API 組合代碼會(huì)讓你迅速墜入回調(diào)地獄。代碼會(huì)變得混亂、難以理解且容易出錯(cuò)。一個(gè)更好的方法是使用響應(yīng)式方法,以一種聲明式樣式編寫 API 網(wǎng)關(guān)代碼。響應(yīng)式抽象概念的例子有 Scala 中的 Future、Java 8 中的 CompletableFuture 和 JavaScript 中的P romise,還有最初微軟為 .NET 平臺(tái)開(kāi)發(fā)的 Reactive Extensions(RX)。Netflix 創(chuàng)建了 RxJava for JVM,專門用于他們的 API 網(wǎng)關(guān)。此外,還有 RxJS for JavaScript,它既可以在瀏覽器中運(yùn)行,也可以在 Node.js 中運(yùn)行。使用響應(yīng)式方法能讓你編寫簡(jiǎn)單但高效的 API 網(wǎng)關(guān)代碼。
服務(wù)調(diào)用
基于微服務(wù)的應(yīng)用程序是一個(gè)分布式系統(tǒng),必須使用一種進(jìn)程間通信機(jī)制。有兩種類型的進(jìn)程間通信機(jī)制可供選擇。一種是使用異步的、基于消息傳遞的機(jī)制。有些實(shí)現(xiàn)使用諸如 JMS 或 AMQP 那樣的消息代理,而其它的實(shí)現(xiàn)(如 Zeromq)則沒(méi)有代理,服務(wù)間直接通信。
另一種進(jìn)程間通信類型是諸如 HTTP 或 Thrift 那樣的同步機(jī)制。通常,一個(gè)系統(tǒng)會(huì)同時(shí)使用異步和同步兩種類型。它甚至還可能使用同一類型的多種實(shí)現(xiàn)。總之,API 網(wǎng)關(guān)需要支持多種通信機(jī)制。
服務(wù)發(fā)現(xiàn)
API 網(wǎng)關(guān)需要知道它與之通信的每個(gè)微服務(wù)的位置(IP 地址和端口)。在傳統(tǒng)的應(yīng)用程序中,或許可以硬連線這個(gè)位置,但在現(xiàn)代的、基于云的微服務(wù)應(yīng)用程序中,這并不是一個(gè)容易解決的問(wèn)題。基礎(chǔ)設(shè)施服務(wù)(如消息代理)通常會(huì)有一個(gè)靜態(tài)位置,可以通過(guò) OS 環(huán)境變量指定。但是,確定一個(gè)應(yīng)用程序服務(wù)的位置沒(méi)有這么簡(jiǎn)單。應(yīng)用程序服務(wù)的位置是動(dòng)態(tài)分配的,而且,單個(gè)服務(wù)的一組實(shí)例也會(huì)隨著自動(dòng)擴(kuò)展或升級(jí)而動(dòng)態(tài)變化。
總之,像系統(tǒng)中的其它服務(wù)客戶端一樣,API 網(wǎng)關(guān)需要使用系統(tǒng)的服務(wù)發(fā)現(xiàn)機(jī)制,可以是服務(wù)器端發(fā)現(xiàn),也可以是客戶端發(fā)現(xiàn)。下一篇文章將更詳細(xì)地描述服務(wù)發(fā)現(xiàn)。現(xiàn)在,需要注意的是,如果系統(tǒng)使用客戶端發(fā)現(xiàn),那么 API 網(wǎng)關(guān)必須能夠查詢服務(wù)注冊(cè)中心,這是一個(gè)包含所有微服務(wù)實(shí)例及其位置的數(shù)據(jù)庫(kù)。
處理局部失敗
在實(shí)現(xiàn) API 網(wǎng)關(guān)時(shí),還需要處理局部失敗的問(wèn)題。該問(wèn)題出現(xiàn)在所有的分布式系統(tǒng)中。當(dāng)一個(gè)服務(wù)調(diào)用另一個(gè)服務(wù),而后者響應(yīng)慢或不可用的時(shí)候,就會(huì)出現(xiàn)這個(gè)問(wèn)題。API 網(wǎng)關(guān)不能因?yàn)闊o(wú)限期地等待下游服務(wù)而阻塞。不過(guò),如何處理失敗取決于特定的場(chǎng)景以及哪個(gè)服務(wù)失敗。例如,在產(chǎn)品詳情場(chǎng)景下,如果推薦服務(wù)無(wú)響應(yīng),那么 API 網(wǎng)關(guān)應(yīng)該向客戶端返回產(chǎn)品詳情的其它內(nèi)容,因?yàn)樗鼈儗?duì)用戶依然有用。推薦內(nèi)容可以為空,也可以用一個(gè)固定的 TOP 10 列表取代。不過(guò),如果產(chǎn)品信息服務(wù)無(wú)響應(yīng),那么 API 網(wǎng)關(guān)應(yīng)該向客戶端返回一個(gè)錯(cuò)誤信息。
如果緩存數(shù)據(jù)可用,那么 API 網(wǎng)關(guān)還可以返回緩存數(shù)據(jù)。例如,鑒于產(chǎn)品價(jià)格不會(huì)頻繁變動(dòng),如果價(jià)格服務(wù)不可用,API 網(wǎng)關(guān)可以返回緩存的價(jià)格數(shù)據(jù)。數(shù)據(jù)可以由 API 網(wǎng)關(guān)自己緩存,也可以存儲(chǔ)在像 Redis 或 Memcached 之類的外部緩存中。通過(guò)返回默認(rèn)數(shù)據(jù)或者緩存數(shù)據(jù),API 網(wǎng)關(guān)可以確保系統(tǒng)故障不影響用戶體驗(yàn)。
在編寫代碼調(diào)用遠(yuǎn)程服務(wù)方面,Netflix Hystrix 是一個(gè)格外有用的庫(kù)。Hystrix 會(huì)暫停超出特定閾限的調(diào)用。它實(shí)現(xiàn)了一個(gè)“斷路器(circuit breaker)”模式,可以防止客戶端對(duì)無(wú)響應(yīng)的服務(wù)進(jìn)行不必要的等待。如果服務(wù)的錯(cuò)誤率超出了設(shè)定的閾值,那么 Hystrix 會(huì)啟動(dòng)斷路器,所有請(qǐng)求會(huì)立即失敗并持續(xù)一定時(shí)間。Hystrix 允許用戶定義一個(gè)請(qǐng)求失敗后的后援操作,比如從緩存讀取數(shù)據(jù),或者返回一個(gè)默認(rèn)值。如果你正在使用 JVM,那么你應(yīng)該考慮使用 Hystrix;如果你正在使用一個(gè)非 JVM 環(huán)境,那么可以使用一個(gè)功能相同的庫(kù)。
總結(jié)
對(duì)于大多數(shù)基于微服務(wù)的應(yīng)用程序而言,實(shí)現(xiàn) API 網(wǎng)關(guān),將其作為系統(tǒng)的唯一入口很有必要。API 網(wǎng)關(guān)負(fù)責(zé)服務(wù)請(qǐng)求路由、組合及協(xié)議轉(zhuǎn)換。它為每個(gè)應(yīng)用程序客戶端提供一個(gè)定制的 API。API 網(wǎng)關(guān)還可以通過(guò)返回緩存數(shù)據(jù)或默認(rèn)數(shù)據(jù)屏蔽后端服務(wù)失敗。在本系列的下一篇文章中,我們將探討服務(wù)間通信
文章轉(zhuǎn)載自:http://blog.daocloud.io/microservices-2/