使用 API 网关
簡(jiǎn)介
我們假設(shè)您正在為一個(gè)購物應(yīng)用開發(fā)一個(gè)原生移動(dòng)客戶端。您可能需要實(shí)現(xiàn)一個(gè)產(chǎn)品詳細(xì)信息頁面,用于展示給定商品的信息。
例如,圖 2-1 展示了在 Amazon 的 Android 移動(dòng)應(yīng)用中滾動(dòng)產(chǎn)品信息時(shí)所看的內(nèi)容。
這是一個(gè)智能手機(jī)應(yīng)用,產(chǎn)品詳細(xì)信息頁面展示了許多信息。不僅有基本的產(chǎn)品信息,如名稱、描述和價(jià)格,頁面還展示了:
- 購物車中的物品數(shù)量
- 訂單歷史
- 客戶評(píng)價(jià)
- 低庫存警告
- 配送選項(xiàng)
- 各種推薦,包括了購買此產(chǎn)品的客戶購買的其他產(chǎn)品
- 選擇性購買選項(xiàng)
在使用單體應(yīng)用架構(gòu)的情況下,移動(dòng)客戶端通過對(duì)應(yīng)用程序進(jìn)行單個(gè) REST 調(diào)用來檢索此數(shù)據(jù),例如:
GET api.company.com/productdetails/productId負(fù)載均衡器將請(qǐng)求路由到幾個(gè)相同應(yīng)用程序?qū)嵗械钠渲幸粋€(gè)。之后,應(yīng)用程序查詢各個(gè)數(shù)據(jù)庫表并返回響應(yīng)給客戶端。相比之下,當(dāng)使用微服務(wù)架構(gòu)時(shí),產(chǎn)品詳細(xì)頁面上展示的數(shù)據(jù)來自多個(gè)微服務(wù)。以下是一些微服務(wù), 可能擁有特定產(chǎn)品頁面展示的數(shù)據(jù):
- 訂單服務(wù) 訂單歷史
- 目錄( catalog)服務(wù) 基本的產(chǎn)品信息,如產(chǎn)品名稱、圖片和價(jià)格
- 評(píng)價(jià)服務(wù) 客戶評(píng)價(jià)
- 庫存服務(wù) 低庫存警告
- 配送服務(wù) 配送選項(xiàng)、期限和費(fèi)用,由配送方的 API 單獨(dú)提供
- 推薦服務(wù) 推薦類目
我們需要決定移動(dòng)客戶端如何訪問這些服務(wù)。讓我們來看看有哪些方法。
客戶端與微服務(wù)直接通信
理論上,客戶端可以直接向每個(gè)微服務(wù)發(fā)送請(qǐng)求。每個(gè)微服務(wù)都有一個(gè)公開的端點(diǎn):
https://serviceName.api.company.name該 URL 將映射到用于跨可用實(shí)例分發(fā)請(qǐng)求的微服務(wù)負(fù)載均衡器。為了檢索特定的產(chǎn)品頁面信息,移動(dòng)客戶端將向上述的每個(gè)微服務(wù)發(fā)送請(qǐng)求。
不幸的是,這種方式存在著挑戰(zhàn)與限制。第一個(gè)問題是客戶端的需求與每個(gè)微服務(wù)暴露的細(xì)粒度的 API 不匹配。 在此示例中,客戶端需要進(jìn)行七次單獨(dú)請(qǐng)求。如果在更加復(fù)雜的應(yīng)用中,它可能需要做更多的工作。例如,Amazon 展示了在產(chǎn)品頁面渲染中如何牽涉到數(shù)百個(gè)微服務(wù)。雖然客戶端可以通過 LAN 發(fā)送許多請(qǐng)求,但在公共互聯(lián)網(wǎng)下效率低下,在移動(dòng)網(wǎng)絡(luò)必然是不切實(shí)際。
客戶端直接調(diào)用微服務(wù)存在的另一個(gè)問題是有些可能使用了非 web 友好協(xié)議。一個(gè)服務(wù)可能使用了 Thrift 二進(jìn)制 RPC,而另一個(gè)則可能使用 AMQP 消息協(xié)議。這兩個(gè)協(xié)議無論是對(duì)瀏覽器還是防火墻而言都是不友好的,最好是在內(nèi)部使用。應(yīng)用程序在防火墻之外應(yīng)該使用 HTTP 或者 WebSocket 之類的協(xié)議。
這種方法的另一個(gè)缺點(diǎn)是它難以重構(gòu)微服務(wù)。隨著時(shí)間推移,我們可能會(huì)想改變系統(tǒng)劃分服務(wù)。例如,我們可能會(huì)合并兩個(gè)服務(wù)或者將服務(wù)拆分為兩個(gè)或者多個(gè)。然而,如果客戶端直接與服務(wù)進(jìn)行通信,實(shí)施這類的重構(gòu)將變得非常困難。
由于存在這些問題,很少有客戶端直接與微服務(wù)進(jìn)行通信。
使用 API 網(wǎng)關(guān)
通常更好的方法是使用 API 網(wǎng)關(guān)。 API 網(wǎng)關(guān)是一個(gè)服務(wù)器,是系統(tǒng)的單入口點(diǎn)。它類似于面向?qū)ο笤O(shè)計(jì)模式中的門面( Facade)模式。 API 網(wǎng)關(guān)封裝了內(nèi)部系統(tǒng)架構(gòu),并針對(duì)每個(gè)客戶端提供一個(gè)定制 API。它還可用于認(rèn)證、監(jiān)控、負(fù)載均衡、緩存和靜態(tài)響應(yīng)處理。
圖 2-3 展示了 API 通常如何整合架構(gòu)
API 網(wǎng)關(guān)負(fù)責(zé)請(qǐng)求路由、組合和協(xié)議轉(zhuǎn)換。所有的客戶端請(qǐng)求首先要通過 API 網(wǎng)關(guān),之后請(qǐng)求被路由到適當(dāng)?shù)姆?wù)。 API 網(wǎng)關(guān)通常會(huì)通過調(diào)用多個(gè)微服務(wù)和聚合結(jié)果來處理一個(gè)請(qǐng)求。它可以在 Web 協(xié)議(如 HTTP 和 WebSocket)和用于內(nèi)部的非 Web 友好協(xié)議之間進(jìn)行轉(zhuǎn)換。
API 還可以為每個(gè)客戶端提供一個(gè)定制 API。它通常會(huì)為移動(dòng)客戶端暴露一個(gè)粗粒度的 API。例如,考慮一下產(chǎn)品詳細(xì)信息場(chǎng)景。API 網(wǎng)關(guān)可以提供一個(gè)端點(diǎn)
/productdetails?productid=xxx如圖 2-3 所示,一個(gè)使用了 API 網(wǎng)關(guān)的微服務(wù)。允許移動(dòng)客戶端通過一個(gè)單獨(dú)的請(qǐng)求來檢索所有產(chǎn)品詳細(xì)信息。 API 網(wǎng)關(guān)通過調(diào)用各種服務(wù)(產(chǎn)品信息、推薦、評(píng)價(jià)等)并組合結(jié)果。
一個(gè)很好的 API 網(wǎng)關(guān)案例是 Netflix API 網(wǎng)關(guān)。 Netflix 流媒體服務(wù)可用于數(shù)百種不同類型的設(shè)備,包括電視機(jī)、機(jī)頂盒、智能手機(jī)、游戲機(jī)和平板電腦等。起初,Netflix 嘗試為他們的流媒體服務(wù)提供一個(gè)通用的 API。后來,他們發(fā)現(xiàn)由于設(shè)備種類繁多,并且他們各自有著不同需求,所以并不是能很好地運(yùn)作。如今,他們使用了 API 網(wǎng)關(guān),通過運(yùn)行特定設(shè)備適配代碼來為每個(gè)設(shè)備提供一個(gè)定制 API。
API 網(wǎng)關(guān)的優(yōu)點(diǎn)與缺點(diǎn)
正如您所料,使用 API 網(wǎng)關(guān)同樣存在好處與壞處。使用 API 網(wǎng)關(guā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)也存在一些缺點(diǎn),它是另一個(gè)高度可用的組件,需要開發(fā)、部署和管理。還另外,還有一個(gè)風(fēng)險(xiǎn)是 API 網(wǎng)關(guān)可能會(huì)成為開發(fā)瓶頸。開發(fā)人員必須更新 API 網(wǎng)關(guān)以暴露每個(gè)微服務(wù)的端點(diǎn)。
重要的是更新 API 網(wǎng)關(guān)的過程應(yīng)盡可能地放緩一些。否則,開發(fā)人員將被迫排隊(duì)等待網(wǎng)關(guān)更新。盡管 API 網(wǎng)關(guān)存在這些缺點(diǎn),但對(duì)于大多數(shù)的真實(shí)應(yīng)用來說,使用 API 是合理的。
實(shí)施 API 網(wǎng)關(guān)
我們已經(jīng)了解了使用 API 網(wǎng)關(guān)的動(dòng)機(jī)與權(quán)衡。接下來讓我們看看您需要考慮的各種設(shè)計(jì)問題。
性能與可擴(kuò)展性
只有少數(shù)公司能達(dá)到 Netflix 的運(yùn)營規(guī)模,每天需要處理數(shù)十億的請(qǐng)求。然而,對(duì)于大多數(shù)應(yīng)用來說, API 網(wǎng)關(guān)的性能和可擴(kuò)展性是相當(dāng)重要的。因此,在一個(gè)支持異步、非阻塞 I/O 平臺(tái)上構(gòu)建 API 網(wǎng)關(guān)是很有必要的。可以使用不同的技術(shù)來實(shí)現(xiàn)一個(gè)可擴(kuò)展的 API 網(wǎng)關(guān)。在 JVM 上,您可以使用基于 NIO 的框架,如 Netty、Vertx、Spring Reactor 或者 JBoss Undertow。一個(gè)流行的非 JVM 選擇是使用 Node.js,它是一個(gè)建立在 Chrome 的 JavaScript 引擎之上的平臺(tái)。
使用響應(yīng)式編程模型
API 網(wǎng)關(guān)通過簡(jiǎn)單地把他們(請(qǐng)求)路由到適當(dāng)?shù)暮蠖朔?wù)來處理一些請(qǐng)求。它通過調(diào)用多個(gè)后端服務(wù)并聚合結(jié)果來處理其他請(qǐng)求。對(duì)于某些請(qǐng)求,如產(chǎn)品詳細(xì)信息請(qǐng)求,對(duì)后端服務(wù)請(qǐng)求而言是彼此獨(dú)立的。為了縮短響應(yīng)時(shí)間到最小, API 網(wǎng)關(guān)應(yīng)該并發(fā)執(zhí)行獨(dú)立請(qǐng)求。
然而,有時(shí)候,請(qǐng)求是相互依賴的。首先, API 網(wǎng)關(guān)可能需要在將請(qǐng)求路由到后端服務(wù)之前, 通過調(diào)用驗(yàn)證服務(wù)來驗(yàn)證請(qǐng)求。同樣,為了從客戶的愿望清單中獲取產(chǎn)品的信息, API 網(wǎng)關(guān)首先必須檢索包含該信息的客戶資料,然后檢索每個(gè)產(chǎn)品的信息。 另一個(gè)有趣的 API 組合案例是 Netflix 視頻網(wǎng)格。
使用傳統(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 中的 Promise。還有 Reactive Extensions(也稱為 Rx 或 ReactiveX),最初由 Microsoft 為 .NET 平臺(tái)開發(fā)。 Netflix 為 JVM 創(chuàng)建了 RxJava,專門應(yīng)用于其 API 網(wǎng)關(guān)。還有用于 JavaScript 的 RxJS,它可以在瀏覽器和 Node.js 中運(yùn)行。使用響應(yīng)式方式可讓您能夠編寫出簡(jiǎn)單而高效的 API 網(wǎng)關(guān)代碼。
服務(wù)調(diào)用
一個(gè)基于微服務(wù)的應(yīng)用程序是一個(gè)分布式系統(tǒng),必須使用一個(gè)進(jìn)程間(inter-process)通信機(jī)制。有兩種進(jìn)程間通信方案。一是使用基于消息的異步機(jī)制。某些實(shí)現(xiàn)采用了消息代理,如 JMS 和 AMQP。其他采用無代理的方式直接與服務(wù)通信,如 Zeromq。
另一種類型的進(jìn)程間通信采用了同步機(jī)制,如 HTTP 和 Thrift。系統(tǒng)通常會(huì)同時(shí)使用異步和同步方式。甚至可以為每種方式應(yīng)用多個(gè)實(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)用程序中,您可以將這些位置硬編碼,但在現(xiàn)代基于云的微服務(wù)應(yīng)用程序中,找到所需的位置不是一件簡(jiǎn)單的事情。
基礎(chǔ)設(shè)施服務(wù)(比如消息代理)通常都有一個(gè)可以通過系統(tǒng)環(huán)境變量來指定的靜態(tài)位置。但是,要確定應(yīng)用程序服務(wù)的位置并不是那么容易。
應(yīng)用服務(wù)可以動(dòng)態(tài)分配位置。此外,由于自動(dòng)擴(kuò)縮和升級(jí),一個(gè)服務(wù)的整組實(shí)例可以動(dòng)態(tài)變更。因此,API 網(wǎng)關(guān)與系統(tǒng)中的任何其他服務(wù)客戶端一樣,需要使用系統(tǒng)的服務(wù)發(fā)現(xiàn)機(jī)制:服務(wù)端發(fā)現(xiàn)或客戶端發(fā)現(xiàn)。現(xiàn)在需要注意的是,如果系統(tǒng)使用客戶端發(fā)現(xiàn),API 網(wǎng)關(guān)必須能夠查詢 服務(wù)注冊(cè)中心,該注冊(cè)中心是所有微服務(wù)實(shí)例及其位置的數(shù)據(jù)庫。
處理局部故障
實(shí)施 API 網(wǎng)關(guān)時(shí)必須解決的另一個(gè)問題是局部故障問題。當(dāng)一個(gè)服務(wù)調(diào)用另一個(gè)響應(yīng)緩慢或者不可用的服務(wù)時(shí),所有分布式系統(tǒng)都會(huì)出現(xiàn)此問題。 API 網(wǎng)關(guān)不應(yīng)該無期限地等待下游服務(wù)。但是,如何處理故障問題取決于特定的方案和哪些服務(wù)發(fā)生故障。例如,如果推薦服務(wù)在獲取產(chǎn)品詳細(xì)信息時(shí)沒有響應(yīng),API 網(wǎng)關(guān)應(yīng)將其余的產(chǎn)品詳細(xì)信息返回給客戶端,因?yàn)樗鼈儗?duì)用戶仍然有用。建議可以是空的,也可以用其他代替,例如硬編碼的十強(qiáng)名單。然而,如果產(chǎn)品信息服務(wù)沒有響應(yīng),那么 API 網(wǎng)關(guān)應(yīng)該向客戶端返回錯(cuò)誤。
如果可以,API 網(wǎng)關(guān)還可以返回緩存數(shù)據(jù)。例如,由于產(chǎn)品價(jià)格變化不大,當(dāng)價(jià)格服務(wù)不可用時(shí),API 網(wǎng)關(guān)可以返回被緩存的價(jià)格數(shù)據(jù)。數(shù)據(jù)可以由 API 網(wǎng)關(guān)緩存或存儲(chǔ)在外部緩存中,如 Redis 或者 Memcached。 API 網(wǎng)關(guān)通過返回默認(rèn)數(shù)據(jù)或緩存數(shù)據(jù),確保系統(tǒng)發(fā)生故障時(shí)最小程度上影響到用戶體驗(yàn)。
Netflix Hystrix 是一個(gè)非常有用的庫,用于編寫調(diào)用遠(yuǎn)程服務(wù)代碼。 Hystrix 可以使超出指定閾值的調(diào)用超時(shí)。它實(shí)現(xiàn)了斷路器模式,防止客戶端不必要地等待無響應(yīng)的服務(wù)。如果服務(wù)的錯(cuò)誤率超過指定閾值, Hystrix 將會(huì)跳閘,所有請(qǐng)求將在指定的時(shí)間內(nèi)立即失敗。 Hystrix 允許您在請(qǐng)求失敗時(shí)定義回退操作,例如從緩存讀取或返回默認(rèn)值。如果您正在使用 JVM,那么您一定要考慮使用 Hystrix。如果您是在非 JVM 環(huán)境中運(yùn)行,則應(yīng)使用同等作用的庫。
總結(jié)
對(duì)于大多數(shù)基于微服務(wù)的應(yīng)用程序來說,實(shí)現(xiàn)一個(gè) API 網(wǎng)關(guān)是很有意義的, API 網(wǎng)關(guān)充當(dāng)著系統(tǒng)的單入口點(diǎn),并且負(fù)責(zé)請(qǐng)求路由,組合和協(xié)議轉(zhuǎn)換。它為每個(gè)應(yīng)用程序客戶端提供了一個(gè)自定義 API。 API 網(wǎng)關(guān)還可以通過返回緩存或默認(rèn)數(shù)據(jù)來掩蓋后端服務(wù)故障。
總結(jié)
- 上一篇: Java编程图书开始了
- 下一篇: 女生做大数据有发展前景吗?能学会吗?