編者的話|本文來自 Nginx 官方博客掸刊,是微服務(wù)系列文章的第二篇琢蛤,本文將探討:微服務(wù)架構(gòu)是如何影響客戶端到服務(wù)端的通信害捕,并提出一種使用 API 網(wǎng)關(guān)的方法。
作者介紹:Chris Richardson墨叛,是世界著名的軟件大師止毕,經(jīng)典技術(shù)著作《POJOS IN ACTION》一書的作者,也是 cloudfoundry.com 最初的創(chuàng)始人漠趁,Chris Richardson 與 Martin Fowler扁凛、Sam Newman、Adrian Cockcroft 等并稱為世界十大軟件架構(gòu)師闯传。
Chris Richardson 所著所有文章已獨家授權(quán) DaoCloud 翻譯并刊載谨朝。
本系列包含 7 篇文章,介紹了微服務(wù)的設(shè)計甥绿、構(gòu)建和部署字币,并與傳統(tǒng)的單體架構(gòu)進行了比較。本系列將分析微服務(wù)架構(gòu)的各種因素共缕,你也將了解微服務(wù)架構(gòu)模型的優(yōu)劣纬朝、是否適合你的項目,以及如何應(yīng)用骄呼。
Chris Richardson 微服務(wù)系列全 7 篇:
本期內(nèi)容
微服務(wù)系列文章的第一篇介紹了微服務(wù)架構(gòu)模式,討論了使用微服務(wù)的優(yōu)缺點判没,以及為什么微服務(wù)雖然復(fù)雜度高卻是復(fù)雜應(yīng)用程序的理想選擇蜓萄。
在決定以一組微服務(wù)來構(gòu)建自己的應(yīng)用時,你需要確定應(yīng)用客戶端如何與微服務(wù)交互澄峰。
在單體式程序中嫉沽,通常只有一組冗余的或者負(fù)載均衡的服務(wù)提供點。在微服務(wù)架構(gòu)中俏竞,每一個微服務(wù)暴露一組細粒度的服務(wù)提供點绸硕。在本篇文章中堂竟,我們來看它如何影響客戶端到服務(wù)端通信,并提出一種使用 API 網(wǎng)關(guān)的方法玻佩。
簡要概述
讓我們想象一下出嘹,你要為一個購物應(yīng)用程序開發(fā)一個原生移動客戶端。你很可能需要實現(xiàn)一個產(chǎn)品詳情頁面咬崔,展示任何指定商品的信息税稼。
下圖展示了 Amazon Android 應(yīng)用在商品詳情頁顯示的內(nèi)容。
即使只是個智能手機應(yīng)用垮斯,產(chǎn)品詳情頁面也顯示了大量的信息郎仆。該頁面不僅包含基本的產(chǎn)品信息(如名稱、描述兜蠕、價格)扰肌,而且還顯示了如下內(nèi)容:
- 購物車中的商品數(shù)量
- 歷史訂單
- 客戶評論
- 低庫存預(yù)警
- 送貨選項
- 各種推薦,包括經(jīng)常與該商品一起購買的其它商品熊杨、購買該商品的客戶購買的其它商品曙旭、購買該商品的客戶看過的其它商品
- 其它的購物選擇
使用單體應(yīng)用程序架構(gòu)時,移動客戶端通過向應(yīng)用程序發(fā)起一次 REST 調(diào)用(GET api.company.com/productdetails/)來獲取這些數(shù)據(jù)猴凹。負(fù)載均衡器將請求路由給 N 個相同的應(yīng)用程序?qū)嵗械钠渲兄灰恼H缓螅瑧?yīng)用程序會查詢各種數(shù)據(jù)庫表郊霎,并將響應(yīng)返回給客戶端沼头。
相反,若是采用微服務(wù)架構(gòu)书劝,顯示在產(chǎn)品頁上的數(shù)據(jù)會分布在不同的微服務(wù)上进倍。下面列舉了可能與產(chǎn)品詳情頁數(shù)據(jù)有關(guān)的一些微服務(wù):
- 購物車服務(wù)——購物車中的件數(shù)
- 訂單服務(wù)——歷史訂單
- 目錄服務(wù)——商品基本信息,如名稱购对、圖片和價格
- 評論服務(wù)——客戶的評論
- 庫存服務(wù)——低庫存預(yù)警
- 送貨服務(wù)——送貨選項猾昆、期限和費用,這些信息單獨從送貨方 API 獲取
- 推薦服務(wù)——推薦商品
我們需要決定移動客戶端如何訪問這些服務(wù)骡苞。讓我們看看有哪些方法垂蜗。
客戶端與微服務(wù)直接通信
從理論上講,客戶端可以直接向每個微服務(wù)發(fā)送請求解幽。每個微服務(wù)都有一個公開的端點(https ://.api.company.name)贴见。該 URL 映射到微服務(wù)的負(fù)載均衡器,由后者負(fù)責(zé)在可用實例之間分發(fā)請求躲株。為了獲取產(chǎn)品詳情片部,移動客戶端將逐一向上文列出的 N 個服務(wù)發(fā)送請求。
遺憾的是霜定,這種方法存在挑戰(zhàn)和局限档悠。問題之一是客戶端需求和每個微服務(wù)暴露的細粒度 API 不匹配廊鸥。在這個例子中,客戶端需要發(fā)送 7 個獨立請求辖所。在更復(fù)雜的應(yīng)用程序中惰说,可能要發(fā)送更多的請求;按照 Amazon 的說法奴烙,他們在顯示他們的產(chǎn)品頁面時就調(diào)用了數(shù)百個服務(wù)助被。然而,客戶端通過 LAN 發(fā)送許多請求切诀,這在公網(wǎng)上可能會很低效揩环,在移動網(wǎng)絡(luò)上就根本不可行。這種方法還使得客戶端代碼非常復(fù)雜幅虑。
客戶端直接調(diào)用微服務(wù)的另一個問題是丰滑,部分服務(wù)使用的協(xié)議對 web 并不友好。一個服務(wù)可能使用 Thrift 二進制 RPC倒庵,而另一個服務(wù)可能使用 AMQP 消息傳遞協(xié)議褒墨。不管哪種協(xié)議對于瀏覽器或防火墻都不夠友好,最好是內(nèi)部使用擎宝。在防火墻之外郁妈,應(yīng)用程序應(yīng)該使用諸如 HTTP 和 WebSocket 之類的協(xié)議。
這種方法的另一個缺點是绍申,它會使得微服務(wù)難以重構(gòu)噩咪。隨著時間推移,我們可能想要更改系統(tǒng)拆分成服務(wù)的方式极阅。例如胃碾,我們可能合并兩個服務(wù),或者將一個服務(wù)拆分成兩個或更多服務(wù)筋搏。然而仆百,如果客戶端與微服務(wù)直接通信,那么執(zhí)行這類重構(gòu)就非常困難了奔脐。
由于上述三種問題的原因俄周,客戶端直接與服務(wù)器端通信的方式很少在實際中使用。
使用 API 網(wǎng)關(guān)構(gòu)建微服務(wù)
通常來說髓迎,使用 API 網(wǎng)關(guān)是更好的解決方式峦朗。API 網(wǎng)關(guān)是一個服務(wù)器,也可以說是進入系統(tǒng)的唯一節(jié)點竖般。這與面向?qū)ο笤O(shè)計模式中的 Facade 模式很像。API 網(wǎng)關(guān)封裝內(nèi)部系統(tǒng)的架構(gòu)茶鹃,并且提供 API 給各個客戶端涣雕。它還可能還具備授權(quán)艰亮、監(jiān)控、負(fù)載均衡挣郭、緩存迄埃、請求分片和管理、靜態(tài)響應(yīng)處理等功能兑障。下圖展示了一個適應(yīng)當(dāng)前架構(gòu)的 API 網(wǎng)關(guān)侄非。
API 網(wǎng)關(guān)負(fù)責(zé)服務(wù)請求路由、組合及協(xié)議轉(zhuǎn)換流译〕言梗客戶端的所有請求都首先經(jīng)過 API 網(wǎng)關(guān),然后由它將請求路由到合適的微服務(wù)福澡。API 網(wǎng)關(guān)經(jīng)常會通過調(diào)用多個微服務(wù)并合并結(jié)果來處理一個請求叠赦。它可以在 web 協(xié)議(如 HTTP 與 WebSocket)與內(nèi)部使用的非 web 友好協(xié)議之間轉(zhuǎn)換。
API 網(wǎng)關(guān)還能為每個客戶端提供一個定制的 API革砸。通常除秀,它會向移動客戶端暴露一個粗粒度的 API。以產(chǎn)品詳情的場景為例算利,API 網(wǎng)關(guān)可以提供一個端點(/productdetails?productid=xxx)册踩,使移動客戶端可以通過一個請求獲取所有的產(chǎn)品詳情。API 網(wǎng)關(guān)通過調(diào)用各個服務(wù)(產(chǎn)品信息效拭、推薦暂吉、評論等等)并合并結(jié)果來處理請求。
Netflix API 網(wǎng)關(guān)是一個很好的 API 網(wǎng)關(guān)實例允耿。Netflix 流媒體服務(wù)提供給成百上千種類型的設(shè)備使用借笙,包括電視、機頂盒较锡、智能手機业稼、游戲系統(tǒng)、平板電腦等等蚂蕴。
最初低散,Netflix 試圖為他們的流媒體服務(wù)提供一個通用的 API。然而他們發(fā)現(xiàn)骡楼,由于各種各樣的設(shè)備都有自己獨特的需求熔号,這種方式并不能很好地工作。如今鸟整,他們使用一個 API 網(wǎng)關(guān)引镊,通過運行與針對特定設(shè)備的適配器代碼,來為每種設(shè)備提供定制的 API。通常弟头,一個適配器通過調(diào)用平均 6 到 7 個后端服務(wù)來處理每個請求吩抓。Netflix API 網(wǎng)關(guān)每天處理數(shù)十億請求。
API 網(wǎng)關(guān)的優(yōu)點和缺點
如你所料赴恨,使用 API 網(wǎng)關(guān)有優(yōu)點也有不足疹娶。使用 API 網(wǎng)關(guān)的最大優(yōu)點是,它封裝了應(yīng)用程序的內(nèi)部結(jié)構(gòu)伦连。客戶端只需要同網(wǎng)關(guān)交互雨饺,而不必調(diào)用特定的服務(wù)。API 網(wǎng)關(guān)為每一類客戶端提供了特定的 API惑淳,這減少了客戶端與應(yīng)用程序間的交互次數(shù)额港,還簡化了客戶端代碼。
API 網(wǎng)關(guān)也有一些不足汛聚。它增加了一個我們必須開發(fā)锹安、部署和維護的高可用組件。還有一個風(fēng)險是倚舀,API 網(wǎng)關(guān)變成了開發(fā)瓶頸叹哭。為了暴露每個微服務(wù)的端點,開發(fā)人員必須更新 API 網(wǎng)關(guān)痕貌。API網(wǎng)關(guān)的更新過程要盡可能地簡單风罩,這很重要;否則舵稠,為了更新網(wǎng)關(guān)超升,開發(fā)人員將不得不排隊等待捷沸。不過赖歌,雖然有這些不足,但對于大多數(shù)現(xiàn)實世界的應(yīng)用程序而言冕广,使用 API 網(wǎng)關(guān)是合理的落追。
實現(xiàn) API 網(wǎng)關(guān)
到目前為止盈滴,我們已經(jīng)探討了使用 API 網(wǎng)關(guān)的動力及其優(yōu)缺點。下面讓我們看一下需要考慮的各種設(shè)計問題轿钠。
性能和可擴展性
只有少數(shù)公司擁有 Netflix 這樣的規(guī)模巢钓,需要每天處理每天需要處理數(shù)十億請求。不管怎樣疗垛,對于大多數(shù)應(yīng)用程序而言症汹,API 網(wǎng)關(guān)的性能和可擴展性都非常重要。因此贷腕,將 API 網(wǎng)關(guān)構(gòu)建在一個支持異步背镇、I/O 非阻塞的平臺上是合理的咬展。有多種不同的技術(shù)可以實現(xiàn)一個可擴展的 API 網(wǎng)關(guān)。在 JVM 上瞒斩,可以使用一種基于 NIO 的框架挚赊,比如 Netty、Vertx济瓢、Spring Reactor 或 JBoss Undertow 中的一種。一個非常流行的非 JVM 選項是 Node.js妹卿,它是一個基于 Chrome JavaScript 引擎構(gòu)建的平臺旺矾。
另一個方法是使用 NGINX Plus。NGINX Plus 提供了一個成熟的夺克、可擴展的箕宙、高性能 web 服務(wù)器和一個易于部署的、可配置可編程的反向代理铺纽。NGINX Plus 可以管理身份驗證柬帕、訪問控制、負(fù)載均衡請求狡门、緩存響應(yīng)陷寝,并提供應(yīng)用程序可感知的健康檢查和監(jiān)控。
使用響應(yīng)式編程模型
API 網(wǎng)關(guān)通過簡單地將請求路由給合適的后端服務(wù)來處理部分請求其馏,而通過調(diào)用多個后端服務(wù)并合并結(jié)果來處理其它請求凤跑。對于部分請求,比如產(chǎn)品詳情相關(guān)的多個請求叛复,它們對后端服務(wù)的請求是獨立于其它請求的仔引。為了最小化響應(yīng)時間,API 網(wǎng)關(guān)應(yīng)該并發(fā)執(zhí)行獨立請求褐奥。
然而咖耘,有時候,請求之間存在依賴撬码。在將請求路由到后端服務(wù)之前儿倒,API 網(wǎng)關(guān)可能首先需要調(diào)用身份驗證服務(wù)驗證請求的合法性。類似地耍群,為了獲取客戶心愿單中的產(chǎn)品信息义桂,API 網(wǎng)關(guān)必須首先獲取包含這些信息的客戶資料,然后再獲取每個產(chǎn)品的信息蹈垢。關(guān)于 API 組合慷吊,另一個有趣的例子是 Netflix Video Grid。
使用傳統(tǒng)的異步回調(diào)方法編寫 API 組合代碼會讓你迅速墜入回調(diào)地獄曹抬。代碼會變得混亂溉瓶、難以理解且容易出錯。一個更好的方法是使用響應(yīng)式方法,以一種聲明式樣式編寫 API 網(wǎng)關(guān)代碼堰酿。響應(yīng)式抽象概念的例子有 Scala 中的 Future疾宏、Java 8 中的 CompletableFuture 和 JavaScript 中的P romise,還有最初微軟為 .NET 平臺開發(fā)的 Reactive Extensions(RX)触创。Netflix 創(chuàng)建了 RxJava for JVM坎藐,專門用于他們的 API 網(wǎng)關(guān)。此外哼绑,還有 RxJS for JavaScript岩馍,它既可以在瀏覽器中運行,也可以在 Node.js 中運行抖韩。使用響應(yīng)式方法能讓你編寫簡單但高效的 API 網(wǎng)關(guān)代碼蛀恩。
服務(wù)調(diào)用
基于微服務(wù)的應(yīng)用程序是一個分布式系統(tǒng),必須使用一種進程間通信機制茂浮。有兩種類型的進程間通信機制可供選擇双谆。一種是使用異步的、基于消息傳遞的機制席揽。有些實現(xiàn)使用諸如 JMS 或 AMQP 那樣的消息代理顽馋,而其它的實現(xiàn)(如 Zeromq)則沒有代理,服務(wù)間直接通信幌羞。
另一種進程間通信類型是諸如 HTTP 或 Thrift 那樣的同步機制趣避。通常,一個系統(tǒng)會同時使用異步和同步兩種類型新翎。它甚至還可能使用同一類型的多種實現(xiàn)程帕。總之地啰,API 網(wǎng)關(guān)需要支持多種通信機制愁拭。
服務(wù)發(fā)現(xiàn)
API 網(wǎng)關(guān)需要知道它與之通信的每個微服務(wù)的位置(IP 地址和端口)。在傳統(tǒng)的應(yīng)用程序中亏吝,或許可以硬連線這個位置岭埠,但在現(xiàn)代的、基于云的微服務(wù)應(yīng)用程序中蔚鸥,這并不是一個容易解決的問題惜论。基礎(chǔ)設(shè)施服務(wù)(如消息代理)通常會有一個靜態(tài)位置止喷,可以通過 OS 環(huán)境變量指定馆类。但是,確定一個應(yīng)用程序服務(wù)的位置沒有這么簡單弹谁。應(yīng)用程序服務(wù)的位置是動態(tài)分配的乾巧,而且句喜,單個服務(wù)的一組實例也會隨著自動擴展或升級而動態(tài)變化。
總之沟于,像系統(tǒng)中的其它服務(wù)客戶端一樣咳胃,API 網(wǎng)關(guān)需要使用系統(tǒng)的服務(wù)發(fā)現(xiàn)機制,可以是服務(wù)器端發(fā)現(xiàn)旷太,也可以是客戶端發(fā)現(xiàn)展懈。下一篇文章將更詳細地描述服務(wù)發(fā)現(xiàn)。現(xiàn)在供璧,需要注意的是标沪,如果系統(tǒng)使用客戶端發(fā)現(xiàn),那么 API 網(wǎng)關(guān)必須能夠查詢服務(wù)注冊中心嗜傅,這是一個包含所有微服務(wù)實例及其位置的數(shù)據(jù)庫。
處理局部失敗
在實現(xiàn) API 網(wǎng)關(guān)時檩赢,還需要處理局部失敗的問題吕嘀。該問題出現(xiàn)在所有的分布式系統(tǒng)中。當(dāng)一個服務(wù)調(diào)用另一個服務(wù)贞瞒,而后者響應(yīng)慢或不可用的時候偶房,就會出現(xiàn)這個問題。API 網(wǎng)關(guān)不能因為無限期地等待下游服務(wù)而阻塞军浆。不過棕洋,如何處理失敗取決于特定的場景以及哪個服務(wù)失敗。例如乒融,在產(chǎn)品詳情場景下掰盘,如果推薦服務(wù)無響應(yīng),那么 API 網(wǎng)關(guān)應(yīng)該向客戶端返回產(chǎn)品詳情的其它內(nèi)容赞季,因為它們對用戶依然有用愧捕。推薦內(nèi)容可以為空,也可以用一個固定的 TOP 10 列表取代申钩。不過次绘,如果產(chǎn)品信息服務(wù)無響應(yīng),那么 API 網(wǎng)關(guān)應(yīng)該向客戶端返回一個錯誤信息撒遣。
如果緩存數(shù)據(jù)可用邮偎,那么 API 網(wǎng)關(guān)還可以返回緩存數(shù)據(jù)。例如义黎,鑒于產(chǎn)品價格不會頻繁變動禾进,如果價格服務(wù)不可用,API 網(wǎng)關(guān)可以返回緩存的價格數(shù)據(jù)廉涕。數(shù)據(jù)可以由 API 網(wǎng)關(guān)自己緩存命迈,也可以存儲在像 Redis 或 Memcached 之類的外部緩存中贩绕。通過返回默認(rèn)數(shù)據(jù)或者緩存數(shù)據(jù),API 網(wǎng)關(guān)可以確保系統(tǒng)故障不影響用戶體驗壶愤。
在編寫代碼調(diào)用遠程服務(wù)方面淑倾,Netflix Hystrix 是一個格外有用的庫。Hystrix 會暫停超出特定閾限的調(diào)用征椒。它實現(xiàn)了一個“斷路器(circuit breaker)”模式娇哆,可以防止客戶端對無響應(yīng)的服務(wù)進行不必要的等待。如果服務(wù)的錯誤率超出了設(shè)定的閾值勃救,那么 Hystrix 會啟動斷路器碍讨,所有請求會立即失敗并持續(xù)一定時間。Hystrix 允許用戶定義一個請求失敗后的后援操作蒙秒,比如從緩存讀取數(shù)據(jù)勃黍,或者返回一個默認(rèn)值。如果你正在使用 JVM晕讲,那么你應(yīng)該考慮使用 Hystrix覆获;如果你正在使用一個非 JVM 環(huán)境,那么可以使用一個功能相同的庫瓢省。
總結(jié)
對于大多數(shù)基于微服務(wù)的應(yīng)用程序而言弄息,實現(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)還可以通過返回緩存數(shù)據(jù)或默認(rèn)數(shù)據(jù)屏蔽后端服務(wù)失敗缨称。在本系列的下一篇文章中,我們將探討服務(wù)間通信祝迂。