希望讀者通過(guò)本系列文章對(duì)微服務(wù)優(yōu)缺點(diǎn)有一個(gè)比較好的理解,以及何時(shí)使用這種架構(gòu)。也許微服務(wù)架構(gòu)比較適合你的應(yīng)用。也許你正在開發(fā)一個(gè)大型、復(fù)雜單體式應(yīng)用,日常開發(fā)和部署經(jīng)驗(yàn)非常緩慢和痛苦,而微服務(wù)看起來(lái)是遠(yuǎn)方一個(gè)極樂(lè)世界。幸運(yùn)的是,有可以參考的脫離苦海的策略,本篇文章中,我將描述如何逐步將單體式應(yīng)用遷移到微服務(wù)架構(gòu)。
本系列七篇文章列表如下:
微服務(wù)實(shí)戰(zhàn)(一):微服務(wù)架構(gòu)的優(yōu)勢(shì)與不足
微服務(wù)實(shí)戰(zhàn)(二):使用API Gateway
微服務(wù)實(shí)戰(zhàn)(三):深入微服務(wù)架構(gòu)的進(jìn)程間通信
微服務(wù)實(shí)戰(zhàn)(四):服務(wù)發(fā)現(xiàn)的可行方案以及實(shí)踐案例
微服務(wù)實(shí)踐(五):微服務(wù)的事件驅(qū)動(dòng)數(shù)據(jù)管理
微服務(wù)實(shí)踐(六):選擇微服務(wù)部署策略
微服務(wù)實(shí)戰(zhàn)(一):微服務(wù)架構(gòu)的優(yōu)勢(shì)與不足
【編者的話】本文來(lái)自Nginx官方博客,是微服務(wù)系列文章的第一篇,主要探討了傳統(tǒng)的單體式應(yīng)用的不足,以及微服務(wù)架構(gòu)的優(yōu)勢(shì)與挑戰(zhàn)。正如作者所說(shuō),微服務(wù)架構(gòu)更適合用于構(gòu)建復(fù)雜的應(yīng)用,盡管它也有自己的不足。
這篇文章作者是Chris Richardson,他是早期基于Java的Amazonite EC2 PaaS平臺(tái)CloudFoundry.com的創(chuàng)始人。現(xiàn)在他為企業(yè)提供如何開發(fā)和部署應(yīng)用的咨詢服務(wù)。他也經(jīng)常在http://microservices.io上發(fā)表有關(guān)微服務(wù)的文章。
微服務(wù)正在博客、社交媒體討論組和會(huì)議演講中獲得越來(lái)越多的關(guān)注,在Gartner的2014 Hype Cycle上它的排名非??壳?。同時(shí),軟件社區(qū)中也有不少持懷疑論者,認(rèn)為微服務(wù)不是什么新東西。Naysayers認(rèn)為這就是SOA架構(gòu)的重新包裝。然而,盡管存在著不同的爭(zhēng)論,微服務(wù)架構(gòu)模式卻正在為敏捷部署以及復(fù)雜企業(yè)應(yīng)用實(shí)施提供巨大的幫助。
這篇博客是關(guān)于如何設(shè)計(jì)、開發(fā)和部署微服務(wù)的七篇系列文章中的第一篇。讀者將會(huì)從中學(xué)到方法,并且和單體式架構(gòu)模式(譯者注:本文中會(huì)將 Monolithic翻譯為單體)進(jìn)行對(duì)比。這一系列文章將描述微服務(wù)架構(gòu)中不同元素。你將了解到微服務(wù)架構(gòu)模式的優(yōu)缺點(diǎn),以便決定是否更好的將微服務(wù)架構(gòu)應(yīng)用到自己的項(xiàng)目中,以及如何應(yīng)用這一模式。
首先我們看看為什么要考慮使用微服務(wù)。
開發(fā)單體式應(yīng)用
假設(shè)你正準(zhǔn)備開發(fā)一款與Uber和Hailo競(jìng)爭(zhēng)的出租車調(diào)度軟件,經(jīng)過(guò)初步會(huì)議和需求分析,你可能會(huì)手動(dòng)或者使用基于Rails、Spring Boot、Play或者M(jìn)aven的生成器開始這個(gè)新項(xiàng)目,它的六邊形架構(gòu)是模塊化的 ,架構(gòu)圖如下:
應(yīng)用核心是業(yè)務(wù)邏輯,由定義服務(wù)、域?qū)ο蠛褪录哪K完成。圍繞著核心的是與外界打交道的適配器。適配器包括數(shù)據(jù)庫(kù)訪問(wèn)組件、生產(chǎn)和處理消息的消息組件,以及提供API或者UI訪問(wèn)支持的web模塊等。
盡管也是模塊化邏輯,但是最終它還是會(huì)打包并部署為單體式應(yīng)用。具體的格式依賴于應(yīng)用語(yǔ)言和框架。例如,許多Java應(yīng)用會(huì)被打包為WAR格式,部署在Tomcat或者Jetty上,而另外一些Java應(yīng)用會(huì)被打包成自包含的JAR格式,同樣,Rails和Node.js會(huì)被打包成層級(jí)目錄。
這種應(yīng)用開發(fā)風(fēng)格很常見,因?yàn)镮DE和其它工具都擅長(zhǎng)開發(fā)一個(gè)簡(jiǎn)單應(yīng)用,這類應(yīng)用也很易于調(diào)試,只需要簡(jiǎn)單運(yùn)行此應(yīng)用,用Selenium鏈接UI就可以完成端到端測(cè)試。單體式應(yīng)用也易于部署,只需要把打包應(yīng)用拷貝到服務(wù)器端,通過(guò)在負(fù)載均衡器后端運(yùn)行多個(gè)拷貝就可以輕松實(shí)現(xiàn)應(yīng)用擴(kuò)展。在早期這類應(yīng)用運(yùn)行的很好。
單體式應(yīng)用的不足
不幸的是,這種簡(jiǎn)單方法卻有很大的局限性。一個(gè)簡(jiǎn)單的應(yīng)用會(huì)隨著時(shí)間推移逐漸變大。在每次的sprint中,開發(fā)團(tuán)隊(duì)都會(huì)面對(duì)新“故事”,然后開發(fā)許多新代碼。幾年后,這個(gè)小而簡(jiǎn)單的應(yīng)用會(huì)變成了一個(gè)巨大的怪物。這兒有一個(gè)例子,我最近和一個(gè)開發(fā)者討論,他正在寫一個(gè)工具,用來(lái)分析他們一個(gè)擁有數(shù)百萬(wàn)行代碼的應(yīng)用中JAR文件之間的依賴關(guān)系。我很確信這個(gè)代碼正是很多開發(fā)者經(jīng)過(guò)多年努力開發(fā)出來(lái)的一個(gè)怪物。
一旦你的應(yīng)用變成一個(gè)又大又復(fù)雜的怪物,那開發(fā)團(tuán)隊(duì)肯定很痛苦。敏捷開發(fā)和部署舉步維艱,其中最主要問(wèn)題就是這個(gè)應(yīng)用太復(fù)雜,以至于任何單個(gè)開發(fā)者都不可能搞懂它。因此,修正bug和正確的添加新功能變的非常困難,并且很耗時(shí)。另外,團(tuán)隊(duì)士氣也會(huì)走下坡路。如果代碼難于理解,就不可能被正確的修改。最終會(huì)走向巨大的、不可理解的泥潭。
單體式應(yīng)用也會(huì)降低開發(fā)速度。應(yīng)用越大,啟動(dòng)時(shí)間會(huì)越長(zhǎng)。比如,最近的一個(gè)調(diào)查表明,有時(shí)候應(yīng)用的啟動(dòng)時(shí)間居然超過(guò)了12分鐘。我還聽說(shuō)某些應(yīng)用需要40分鐘啟動(dòng)時(shí)間。如果開發(fā)者需要經(jīng)常重啟應(yīng)用,那么大部分時(shí)間就要在等待中渡過(guò),生產(chǎn)效率受到極大影響。
另外,復(fù)雜而巨大的單體式應(yīng)用也不利于持續(xù)性開發(fā)。今天,SaaS應(yīng)用常態(tài)就是每天會(huì)改變很多次,而這對(duì)于單體式應(yīng)用模式非常困難。另外,這種變化帶來(lái)的影響并沒(méi)有很好的被理解,所以不得不做很多手工測(cè)試。那么接下來(lái),持續(xù)部署也會(huì)很艱難。
單體式應(yīng)用在不同模塊發(fā)生資源沖突時(shí),擴(kuò)展將會(huì)非常困難。比如,一個(gè)模塊完成一個(gè)CPU敏感邏輯,應(yīng)該部署在AWS EC2 Compute Optimized instances,而另外一個(gè)內(nèi)存數(shù)據(jù)庫(kù)模塊更合適于EC2 Memory-optimized instances。然而,由于這些模塊部署在一起,因此不得不在硬件選擇上做一個(gè)妥協(xié)。
單體式應(yīng)用另外一個(gè)問(wèn)題是可靠性。因?yàn)樗心K都運(yùn)行在一個(gè)進(jìn)程中,任何一個(gè)模塊中的一個(gè)bug,比如內(nèi)存泄露,將會(huì)有可能弄垮整個(gè)進(jìn)程。除此之外,因?yàn)樗袘?yīng)用實(shí)例都是唯一的,這個(gè)bug將會(huì)影響到整個(gè)應(yīng)用的可靠性。
最后,單體式應(yīng)用使得采用新架構(gòu)和語(yǔ)言非常困難。比如,設(shè)想你有兩百萬(wàn)行采用XYZ框架寫的代碼。如果想改成ABC框架,無(wú)論是時(shí)間還是成本都是非常昂貴的,即使ABC框架更好。因此,這是一個(gè)無(wú)法逾越的鴻溝。你不得不在最初選擇面前低頭。
總結(jié)一下:一開始你有一個(gè)很成功的關(guān)鍵業(yè)務(wù)應(yīng)用,后來(lái)就變成了一個(gè)巨大的,無(wú)法理解的怪物。因?yàn)椴捎眠^(guò)時(shí)的,效率低的技術(shù),使得雇傭有潛力的開發(fā)者很困難。應(yīng)用無(wú)法擴(kuò)展,可靠性很低,最終,敏捷性開發(fā)和部署變的無(wú)法完成。
那么如何應(yīng)對(duì)呢?
微處理架構(gòu)——處理復(fù)雜事物
許多公司,比如Amazon、eBay和NetFlix,通過(guò)采用微處理結(jié)構(gòu)模式解決了上述問(wèn)題。其思路不是開發(fā)一個(gè)巨大的單體式的應(yīng)用,而是將應(yīng)用分解為小的、互相連接的微服務(wù)。
一個(gè)微服務(wù)一般完成某個(gè)特定的功能,比如下單管理、客戶管理等等。每一個(gè)微服務(wù)都是微型六角形應(yīng)用,都有自己的業(yè)務(wù)邏輯和適配器。一些微服務(wù)還會(huì)發(fā)布API給其它微服務(wù)和應(yīng)用客戶端使用。其它微服務(wù)完成一個(gè)Web UI,運(yùn)行時(shí),每一個(gè)實(shí)例可能是一個(gè)云VM或者是Docker容器。
比如,一個(gè)前面描述系統(tǒng)可能的分解如下:
每一個(gè)應(yīng)用功能區(qū)都使用微服務(wù)完成,另外,Web應(yīng)用會(huì)被拆分成一系列簡(jiǎn)單的Web應(yīng)用(比如一個(gè)對(duì)乘客,一個(gè)對(duì)出租車駕駛員)。這樣的拆分對(duì)于不同用戶、設(shè)備和特殊應(yīng)用場(chǎng)景部署都更容易。
每一個(gè)后臺(tái)服務(wù)開放一個(gè)REST API,許多服務(wù)本身也采用了其它服務(wù)提供的API。比如,駕駛員管理使用了告知駕駛員一個(gè)潛在需求的通知服務(wù)。UI服務(wù)激活其它服務(wù)來(lái)更新Web頁(yè)面。所有服務(wù)都是采用異步的,基于消息的通訊。微服務(wù)內(nèi)部機(jī)制將會(huì)在后續(xù)系列中討論。
一些REST API也對(duì)乘客和駕駛員采用的移動(dòng)應(yīng)用開放。這些應(yīng)用并不直接訪問(wèn)后臺(tái)服務(wù),而是通過(guò)API Gateway來(lái)傳遞中間消息。API Gateway負(fù)責(zé)負(fù)載均衡、緩存、訪問(wèn)控制、API 計(jì)費(fèi)監(jiān)控等等任務(wù),可以通過(guò)NGINX方便實(shí)現(xiàn),后續(xù)文章將會(huì)介紹到API Gateway。
微服務(wù)架構(gòu)模式在上圖中對(duì)應(yīng)于代表可擴(kuò)展Scale Cube的Y軸,這是一個(gè)在《The Art of Scalability》書中描述過(guò)的三維擴(kuò)展模型。另外兩個(gè)可擴(kuò)展軸,X軸由負(fù)載均衡器后端運(yùn)行的多個(gè)應(yīng)用副本組成,Z軸是將需求路由到相關(guān)服務(wù)。
應(yīng)用基本可以用以上三個(gè)維度來(lái)表示,Y軸代表將應(yīng)用分解為微服務(wù)。運(yùn)行時(shí),X軸代表運(yùn)行多個(gè)隱藏在負(fù)載均衡器之后的實(shí)例,提供吞吐能力。一些應(yīng)用可能還是用Z軸將服務(wù)分區(qū)。下面的圖演示行程管理服務(wù)如何部署在運(yùn)行于AWS EC2上的Docker上。
運(yùn)行時(shí),行程管理服務(wù)由多個(gè)服務(wù)實(shí)例構(gòu)成。每一個(gè)服務(wù)實(shí)例都是一個(gè)Docker容器。為了保證高可用,這些容器一般都運(yùn)行在多個(gè)云VM上。服務(wù)實(shí)例前是一層諸如NGINX的負(fù)載均衡器,他們負(fù)責(zé)在各個(gè)實(shí)例間分發(fā)請(qǐng)求。負(fù)載均衡器也同時(shí)處理其它請(qǐng)求,例如緩存、權(quán)限控制、API統(tǒng)計(jì)和監(jiān)控。
這種微服務(wù)架構(gòu)模式深刻影響了應(yīng)用和數(shù)據(jù)庫(kù)之間的關(guān)系,不像傳統(tǒng)多個(gè)服務(wù)共享一個(gè)數(shù)據(jù)庫(kù),微服務(wù)架構(gòu)每個(gè)服務(wù)都有自己的數(shù)據(jù)庫(kù)。另外,這種思路也影響到了企業(yè)級(jí)數(shù)據(jù)模式。同時(shí),這種模式意味著多份數(shù)據(jù),但是,如果你想獲得微服務(wù)帶來(lái)的好處,每個(gè)服務(wù)獨(dú)有一個(gè)數(shù)據(jù)庫(kù)是必須的,因?yàn)檫@種架構(gòu)需要這種松耦合。下面的圖演示示例應(yīng)用數(shù)據(jù)庫(kù)架構(gòu)。
每種服務(wù)都有自己的數(shù)據(jù)庫(kù),另外,每種服務(wù)可以用更適合自己的數(shù)據(jù)庫(kù)類型,也被稱作多語(yǔ)言一致性架構(gòu)。比如,駕駛員管理(發(fā)現(xiàn)哪個(gè)駕駛員更靠近乘客),必須使用支持地理信息查詢的數(shù)據(jù)庫(kù)。
表面上看來(lái),微服務(wù)架構(gòu)模式有點(diǎn)像SOA,他們都由多個(gè)服務(wù)構(gòu)成。但是,可以從另外一個(gè)角度看此問(wèn)題,微服務(wù)架構(gòu)模式是一個(gè)不包含Web服務(wù)(WS-)和ESB服務(wù)的SOA。微服務(wù)應(yīng)用樂(lè)于采用簡(jiǎn)單輕量級(jí)協(xié)議,比如REST,而不是WS-,在微服務(wù)內(nèi)部避免使用ESB以及ESB類似功能。微服務(wù)架構(gòu)模式也拒絕使用canonical schema等SOA概念。
微服務(wù)架構(gòu)的好處
微服務(wù)架構(gòu)模式有很多好處。首先,通過(guò)分解巨大單體式應(yīng)用為多個(gè)服務(wù)方法解決了復(fù)雜性問(wèn)題。在功能不變的情況下,應(yīng)用被分解為多個(gè)可管理的分支或服務(wù)。每個(gè)服務(wù)都有一個(gè)用RPC-或者消息驅(qū)動(dòng)API定義清楚的邊界。微服務(wù)架構(gòu)模式給采用單體式編碼方式很難實(shí)現(xiàn)的功能提供了模塊化的解決方案,由此,單個(gè)服務(wù)很容易開發(fā)、理解和維護(hù)。
第二,這種架構(gòu)使得每個(gè)服務(wù)都可以有專門開發(fā)團(tuán)隊(duì)來(lái)開發(fā)。開發(fā)者可以自由選擇開發(fā)技術(shù),提供API服務(wù)。當(dāng)然,許多公司試圖避免混亂,只提供某些技術(shù)選擇。然后,這種自由意味著開發(fā)者不需要被迫使用某項(xiàng)目開始時(shí)采用的過(guò)時(shí)技術(shù),他們可以選擇現(xiàn)在的技術(shù)。甚至于,因?yàn)榉?wù)都是相對(duì)簡(jiǎn)單,即使用現(xiàn)在技術(shù)重寫以前代碼也不是很困難的事情。
第三,微服務(wù)架構(gòu)模式是每個(gè)微服務(wù)獨(dú)立的部署。開發(fā)者不再需要協(xié)調(diào)其它服務(wù)部署對(duì)本服務(wù)的影響。這種改變可以加快部署速度。UI團(tuán)隊(duì)可以采用AB測(cè)試,快速的部署變化。微服務(wù)架構(gòu)模式使得持續(xù)化部署成為可能。
最后,微服務(wù)架構(gòu)模式使得每個(gè)服務(wù)獨(dú)立擴(kuò)展。你可以根據(jù)每個(gè)服務(wù)的規(guī)模來(lái)部署滿足需求的規(guī)模。甚至于,你可以使用更適合于服務(wù)資源需求的硬件。比如,你可以在EC2 Compute Optimized instances上部署CPU敏感的服務(wù),而在EC2 memory-optimized instances上部署內(nèi)存數(shù)據(jù)庫(kù)。
微服務(wù)架構(gòu)的不足
Fred Brooks在30年前寫道,“there are no silver bullets”,像任何其它科技一樣,微服務(wù)架構(gòu)也有不足。其中一個(gè)跟他的名字類似,『微服務(wù)』強(qiáng)調(diào)了服務(wù)大小,實(shí)際上,有一些開發(fā)者鼓吹建立稍微大一些的,10-100 LOC服務(wù)組。盡管小服務(wù)更樂(lè)于被采用,但是不要忘了這只是終端的選擇而不是最終的目的。微服務(wù)的目的是有效的拆分應(yīng)用,實(shí)現(xiàn)敏捷開發(fā)和部署。
另外一個(gè)主要的不足是,微服務(wù)應(yīng)用是分布式系統(tǒng),由此會(huì)帶來(lái)固有的復(fù)雜性。開發(fā)者需要在RPC或者消息傳遞之間選擇并完成進(jìn)程間通訊機(jī)制。更甚于,他們必須寫代碼來(lái)處理消息傳遞中速度過(guò)慢或者不可用等局部失效問(wèn)題。當(dāng)然這并不是什么難事,但相對(duì)于單體式應(yīng)用中通過(guò)語(yǔ)言層級(jí)的方法或者進(jìn)程調(diào)用,微服務(wù)下這種技術(shù)顯得更復(fù)雜一些。
另外一個(gè)關(guān)于微服務(wù)的挑戰(zhàn)來(lái)自于分區(qū)的數(shù)據(jù)庫(kù)架構(gòu)。商業(yè)交易中同時(shí)給多個(gè)業(yè)務(wù)分主體更新消息很普遍。這種交易對(duì)于單體式應(yīng)用來(lái)說(shuō)很容易,因?yàn)橹挥幸粋€(gè)數(shù)據(jù)庫(kù)。在微服務(wù)架構(gòu)應(yīng)用中,需要更新不同服務(wù)所使用的不同的數(shù)據(jù)庫(kù)。使用分布式交易并不一定是好的選擇,不僅僅是因?yàn)镃AP理論,還因?yàn)榻裉旄邤U(kuò)展性的NoSQL數(shù)據(jù)庫(kù)和消息傳遞中間件并不支持這一需求。最終你不得不使用一個(gè)最終一致性的方法,從而對(duì)開發(fā)者提出了更高的要求和挑戰(zhàn)。
測(cè)試一個(gè)基于微服務(wù)架構(gòu)的應(yīng)用也是很復(fù)雜的任務(wù)。比如,采用流行的Spring Boot架構(gòu),對(duì)一個(gè)單體式web應(yīng)用,測(cè)試它的REST API,是很容易的事情。反過(guò)來(lái),同樣的服務(wù)測(cè)試需要啟動(dòng)和它有關(guān)的所有服務(wù)(至少需要這些服務(wù)的stubs)。再重申一次,不能低估了采用微服務(wù)架構(gòu)帶來(lái)的復(fù)雜性。
另外一個(gè)挑戰(zhàn)在于,微服務(wù)架構(gòu)模式應(yīng)用的改變將會(huì)波及多個(gè)服務(wù)。比如,假設(shè)你在完成一個(gè)案例,需要修改服務(wù)A、B、C,而A依賴B,B依賴C。在單體式應(yīng)用中,你只需要改變相關(guān)模塊,整合變化,部署就好了。對(duì)比之下,微服務(wù)架構(gòu)模式就需要考慮相關(guān)改變對(duì)不同服務(wù)的影響。比如,你需要更新服務(wù)C,然后是B,最后才是A,幸運(yùn)的是,許多改變一般只影響一個(gè)服務(wù),而需要協(xié)調(diào)多服務(wù)的改變很少。
部署一個(gè)微服務(wù)應(yīng)用也很復(fù)雜,一個(gè)分布式應(yīng)用只需要簡(jiǎn)單在復(fù)雜均衡器后面部署各自的服務(wù)器就好了。每個(gè)應(yīng)用實(shí)例是需要配置諸如數(shù)據(jù)庫(kù)和消息中間件等基礎(chǔ)服務(wù)。相對(duì)比,一個(gè)微服務(wù)應(yīng)用一般由大批服務(wù)構(gòu)成。例如,根據(jù)Adrian Cockcroft,Hailo有160個(gè)不同服務(wù)構(gòu)成,NetFlix有大約600個(gè)服務(wù)。每個(gè)服務(wù)都有多個(gè)實(shí)例。這就造成許多需要配置、部署、擴(kuò)展和監(jiān)控的部分,除此之外,你還需要完成一個(gè)服務(wù)發(fā)現(xiàn)機(jī)制(后續(xù)文章中發(fā)表),以用來(lái)發(fā)現(xiàn)與它通訊服務(wù)的地址(包括服務(wù)器地址和端口)。傳統(tǒng)的解決問(wèn)題辦法不能用于解決這么復(fù)雜的問(wèn)題。接續(xù)而來(lái),成功部署一個(gè)微服務(wù)應(yīng)用需要開發(fā)者有足夠的控制部署方法,并高度自動(dòng)化。
一種自動(dòng)化方法是使用PaaS服務(wù),例如Cloud Foundry。PaaS給開發(fā)者提供一個(gè)部署和管理微服務(wù)的簡(jiǎn)單方法,它把所有這些問(wèn)題都打包內(nèi)置解決了。同時(shí),配置PaaS的系統(tǒng)和網(wǎng)絡(luò)專家可以采用最佳實(shí)踐和策略來(lái)簡(jiǎn)化這些問(wèn)題。另外一個(gè)自動(dòng)部署微服務(wù)應(yīng)用的方法是開發(fā)對(duì)于你來(lái)說(shuō)最基礎(chǔ)的PaaS系統(tǒng)。一個(gè)典型的開始點(diǎn)是使用一個(gè)集群化方案,比如配合Docker使用Mesos或者Kubernetes。后面的系列我們會(huì)看看如何基于軟件部署方法例如NGINX,可以方便的在微服務(wù)層面提供緩存、權(quán)限控制、API統(tǒng)計(jì)和監(jiān)控。
總結(jié)
構(gòu)建復(fù)雜的應(yīng)用真的是非常困難。單體式的架構(gòu)更適合輕量級(jí)的簡(jiǎn)單應(yīng)用。如果你用它來(lái)開發(fā)復(fù)雜應(yīng)用,那真的會(huì)很糟糕。微服務(wù)架構(gòu)模式可以用來(lái)構(gòu)建復(fù)雜應(yīng)用,當(dāng)然,這種架構(gòu)模型也有自己的缺點(diǎn)和挑戰(zhàn)。
在后續(xù)的博客中,我會(huì)深入探索微服務(wù)架構(gòu)模式,并討論諸如服務(wù)發(fā)現(xiàn)、服務(wù)部署選擇和如何分解一個(gè)分布式應(yīng)用為多個(gè)服務(wù)的策略。
待續(xù)。。。。
原文鏈接:Introduction to Microservices(翻譯:楊峰 校對(duì):郭蕾)
微服務(wù)實(shí)戰(zhàn)(二):使用API Gateway
【編者的話】本系列的第一篇介紹了微服務(wù)架構(gòu)模式。它討論了采用微服務(wù)的優(yōu)點(diǎn)和缺點(diǎn),除了一些復(fù)雜的微服務(wù),這種模式還是復(fù)雜應(yīng)用的理想選擇。
當(dāng)你決定將應(yīng)用作為一組微服務(wù)時(shí),需要決定應(yīng)用客戶端如何與微服務(wù)交互。在單體式程序中,通常只有一組冗余的或者負(fù)載均衡的服務(wù)提供點(diǎn)。在微服務(wù)架構(gòu)中,每一個(gè)微服務(wù)暴露一組細(xì)粒度的服務(wù)提供點(diǎn)。在本篇文章中,我們來(lái)看它如何影響客戶端到服務(wù)端通信,同時(shí)提出一種API Gateway的方法。
假定你正在為在線購(gòu)物應(yīng)用開發(fā)一個(gè)原生手機(jī)客戶端。你需要實(shí)現(xiàn)一個(gè)產(chǎn)品最終頁(yè)來(lái)展示商品信息。
例如,下面的圖展示了你在亞馬遜Android客戶端上滑動(dòng)產(chǎn)品最終頁(yè)時(shí)看到的信息。
雖然這是一個(gè)智能手機(jī)應(yīng)用,這個(gè)產(chǎn)品最終頁(yè)展示了非常多的信息。例如,不僅這里有產(chǎn)品基本信息(名字、描述和價(jià)格),還有以下內(nèi)容:
購(gòu)物車中的物品數(shù)
下單歷史
用戶評(píng)論
低庫(kù)存警告
快遞選項(xiàng)
各式各樣的推薦,包括經(jīng)常跟這個(gè)物品一起被購(gòu)買的產(chǎn)品、購(gòu)買該物品的其他顧客購(gòu)買的產(chǎn)品以及購(gòu)買該產(chǎn)品的顧客還瀏覽了哪些產(chǎn)品。
可選的購(gòu)物選項(xiàng)
當(dāng)采用一個(gè)單體式應(yīng)用架構(gòu),一個(gè)移動(dòng)客戶端將會(huì)通過(guò)一個(gè)REST請(qǐng)求(GET api.company.com/productdetails/productId)來(lái)獲取這些數(shù)據(jù)。一個(gè)負(fù)載均衡將請(qǐng)求分發(fā)到多個(gè)應(yīng)用實(shí)例之一。應(yīng)用將查詢各種數(shù)據(jù)庫(kù)并返回請(qǐng)求給客戶端。
相對(duì)的,若是采用微服務(wù)架構(gòu),最終頁(yè)上的數(shù)據(jù)會(huì)分布在不同的微服務(wù)上。下面列舉了可能與產(chǎn)品最終頁(yè)數(shù)據(jù)有關(guān)的一些微服務(wù):
購(gòu)物車服務(wù) -- 購(gòu)物車中的物品數(shù)
下單服務(wù) -- 下單歷史
分類服務(wù) -- 基本產(chǎn)品信息,如名字、圖片和價(jià)格
評(píng)論服務(wù) -- 用戶評(píng)論
庫(kù)存服務(wù) -- 低庫(kù)存警告
快遞服務(wù) -- 快遞選項(xiàng)、截止時(shí)間、來(lái)自不同快遞API的成本計(jì)算
推薦服務(wù) -- 推薦產(chǎn)品
我們需要決定移動(dòng)客戶端如何訪問(wèn)這些服務(wù)。請(qǐng)看下面這幾種方式
理論上說(shuō),一個(gè)客戶端可以直接給多個(gè)微服務(wù)中的任何一個(gè)發(fā)起請(qǐng)求。每一個(gè)微服務(wù)都會(huì)有一個(gè)對(duì)外服務(wù)端(https://serviceName.api.company.name)。這個(gè)URL可能會(huì)映射到微服務(wù)的負(fù)載均衡上,它再轉(zhuǎn)發(fā)請(qǐng)求到具體節(jié)點(diǎn)上。為了搜索產(chǎn)品細(xì)節(jié),移動(dòng)端需要向上述微服務(wù)逐個(gè)發(fā)請(qǐng)求。
不幸的是,這個(gè)方案有很多困難和限制。其中一個(gè)問(wèn)題是客戶端的需求量與每個(gè)微服務(wù)暴露的細(xì)粒度API數(shù)量的不匹配。如圖中,客戶端需要7次單獨(dú)請(qǐng)求。在更復(fù)雜的場(chǎng)景中,可能會(huì)需要更多次請(qǐng)求。例如,亞馬遜的產(chǎn)品最終頁(yè)要請(qǐng)求數(shù)百個(gè)微服務(wù)。雖然一個(gè)客戶端可以通過(guò)LAN發(fā)起很多個(gè)請(qǐng)求,但是在公網(wǎng)上這樣會(huì)很沒(méi)有效率,這個(gè)問(wèn)題在移動(dòng)互聯(lián)網(wǎng)上尤為突出。這個(gè)方案同時(shí)會(huì)導(dǎo)致客戶端代碼非常復(fù)雜。
另一個(gè)存在的問(wèn)題是客戶端直接請(qǐng)求微服務(wù)的協(xié)議可能并不是web友好型。一個(gè)服務(wù)可能是用Thrift的RPC協(xié)議,而另一個(gè)服務(wù)可能是用AMQP消息協(xié)議。它們都不是瀏覽或防火墻友好的,并且最好是內(nèi)部使用。應(yīng)用應(yīng)該在防火墻外采用類似HTTP或者WEBSocket協(xié)議。
這個(gè)方案的另一個(gè)缺點(diǎn)是它很難重構(gòu)微服務(wù)。隨著時(shí)間的推移,我們可能需要改變系統(tǒng)微服務(wù)目前的切分方案。例如,我們可能需要將兩個(gè)服務(wù)合并或者將一個(gè)服務(wù)拆分為多個(gè)。但是,如果客戶端直接與微服務(wù)交互,那么這種重構(gòu)就很難實(shí)施。
由于上述三種問(wèn)題的原因,客戶端直接與服務(wù)器端通信的方式很少在實(shí)際中使用。
通常來(lái)說(shuō),一個(gè)更好的解決辦法是采用API Gateway的方式。API Gateway是一個(gè)服務(wù)器,也可以說(shuō)是進(jìn)入系統(tǒng)的唯一節(jié)點(diǎn)。這跟面向?qū)ο笤O(shè)計(jì)模式中的Facade模式很像。API Gateway封裝內(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 Gateway。
API Gateway負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)、合成和協(xié)議轉(zhuǎn)換。所有來(lái)自客戶端的請(qǐng)求都要先經(jīng)過(guò)API Gateway,然后路由這些請(qǐng)求到對(duì)應(yīng)的微服務(wù)。API Gateway將經(jīng)常通過(guò)調(diào)用多個(gè)微服務(wù)來(lái)處理一個(gè)請(qǐng)求以及聚合多個(gè)服務(wù)的結(jié)果。它可以在web協(xié)議與內(nèi)部使用的非Web友好型協(xié)議間進(jìn)行轉(zhuǎn)換,如HTTP協(xié)議、WebSocket協(xié)議。
API Gateway可以提供給客戶端一個(gè)定制化的API。它暴露一個(gè)粗粒度API給移動(dòng)客戶端。以產(chǎn)品最終頁(yè)這個(gè)使用場(chǎng)景為例。API Gateway提供一個(gè)服務(wù)提供點(diǎn)(/productdetails?productid=xxx)使得移動(dòng)客戶端可以在一個(gè)請(qǐng)求中檢索到產(chǎn)品最終頁(yè)的全部數(shù)據(jù)。API Gateway通過(guò)調(diào)用多個(gè)服務(wù)來(lái)處理這一個(gè)請(qǐng)求并返回結(jié)果,涉及產(chǎn)品信息、推薦、評(píng)論等。
一個(gè)很好的API Gateway例子是Netfix API Gateway。Netflix流服務(wù)提供數(shù)百個(gè)不同的微服務(wù),包括電視、機(jī)頂盒、智能手機(jī)、游戲系統(tǒng)、平板電腦等。起初,Netflix視圖提供一個(gè)適用全場(chǎng)景的API。但是,他們發(fā)現(xiàn)這種形式不好用,因?yàn)樯婕暗礁魇礁鳂拥脑O(shè)備以及它們獨(dú)特的需求?,F(xiàn)在,他們采用一個(gè)API Gateway來(lái)提供容錯(cuò)性高的API,針對(duì)不同類型設(shè)備有相應(yīng)代碼。事實(shí)上,一個(gè)適配器處理一個(gè)請(qǐng)求平均要調(diào)用6到8個(gè)后端服務(wù)。Netflix API Gateway每天處理數(shù)十億的請(qǐng)求。
API Gateway的優(yōu)點(diǎn)和缺點(diǎn)
如你所料,采用API Gateway也是優(yōu)缺點(diǎn)并存的。API Gateway的一個(gè)最大好處是封裝應(yīng)用內(nèi)部結(jié)構(gòu)。相比起來(lái)調(diào)用指定的服務(wù),客戶端直接跟gatway交互更簡(jiǎn)單點(diǎn)。API Gateway提供給每一個(gè)客戶端一個(gè)特定API,這樣減少了客戶端與服務(wù)器端的通信次數(shù),也簡(jiǎn)化了客戶端代碼。
API Gateway也有一些缺點(diǎn)。它是一個(gè)高可用的組件,必須要開發(fā)、部署和管理。還有一個(gè)問(wèn)題,它可能成為開發(fā)的一個(gè)瓶頸。開發(fā)者必須更新API Gateway來(lái)提供新服務(wù)提供點(diǎn)來(lái)支持新暴露的微服務(wù)。更新API Gateway時(shí)必須越輕量級(jí)越好。否則,開發(fā)者將因?yàn)楦翯ateway而排隊(duì)列。但是,除了這些缺點(diǎn),對(duì)于大部分的應(yīng)用,采用API Gateway的方式都是有效的。
實(shí)現(xiàn)一個(gè)API Gateway
既然我們已經(jīng)知道了采用API Gateway的動(dòng)機(jī)和優(yōu)缺點(diǎn),下面來(lái)看在設(shè)計(jì)它時(shí)需要考慮哪些事情。
性能和可擴(kuò)展性
只有少數(shù)公司需要處理像Netflix那樣的規(guī)模,每天需要處理數(shù)十億的請(qǐng)求。但是,對(duì)于大多數(shù)應(yīng)用,API Gateway的性能和可擴(kuò)展性也是非常重要的。因此,創(chuàng)建一個(gè)支持同步、非阻塞I/O的API Gateway是有意義的。已經(jīng)有不同的技術(shù)可以用來(lái)實(shí)現(xiàn)一個(gè)可擴(kuò)展的API Gateway。在JVM上,采用基于NIO技術(shù)的框架,如Netty,Vertx,Spring Reactor或者JBoss Undertow。Node.js是一個(gè)非JVM的流行平臺(tái),它是一個(gè)在Chrome的JavaScript引擎基礎(chǔ)上建立的平臺(tái)。一個(gè)可選的方案是NGINX Plus。NGINX Plus提供一個(gè)成熟的、可擴(kuò)展的、高性能web服務(wù)器和反向代理,它們均容易部署、配置和二次開發(fā)。NGINX Plus可以管理授權(quán)、權(quán)限控制、負(fù)載均衡、緩存并提供應(yīng)用健康檢查和監(jiān)控。
采用反應(yīng)性編程模型
對(duì)于有些請(qǐng)求,API Gateway可以通過(guò)直接路由請(qǐng)求到對(duì)應(yīng)的后端服務(wù)上的方式來(lái)處理。對(duì)于另外一些請(qǐng)求,它需要調(diào)用多個(gè)后端服務(wù)并合并結(jié)果來(lái)處理。對(duì)于一些請(qǐng)求,例如產(chǎn)品最終頁(yè)面請(qǐng)求,發(fā)給后端服務(wù)的請(qǐng)求是相互獨(dú)立的。為了最小化響應(yīng)時(shí)間,API Gateway應(yīng)該并發(fā)的處理相互獨(dú)立的請(qǐng)求。但是,有時(shí)候請(qǐng)求之間是有依賴的。API Gateway可能需要先通過(guò)授權(quán)服務(wù)來(lái)驗(yàn)證請(qǐng)求,然后在路由到后端服務(wù)。類似的,為了獲得客戶的產(chǎn)品愿望清單,需要先獲取該用戶的資料,然后返回清單上產(chǎn)品的信息。這樣的一個(gè)API 組件是Netflix Video Grid。
利用傳統(tǒng)的同步回調(diào)方法來(lái)實(shí)現(xiàn)API合并的代碼會(huì)使得你進(jìn)入回調(diào)函數(shù)的噩夢(mèng)中。這種代碼將非常難度且難以維護(hù)。一個(gè)優(yōu)雅的解決方案是采用反應(yīng)性編程模式來(lái)實(shí)現(xiàn)。類似的反應(yīng)抽象實(shí)現(xiàn)有Scala的Future,Java8的CompletableFuture和JavaScript的Promise?;谖④?Net平臺(tái)的有Reactive Extensions(Rx)。Netflix為JVM環(huán)境創(chuàng)建了RxJava來(lái)使用他們的API Gateway。同樣地,JavaScript平臺(tái)有RxJS,可以在瀏覽器和Node.js平臺(tái)上運(yùn)行。采用反應(yīng)編程方法可以幫助快速實(shí)現(xiàn)一個(gè)高效的API Gateway代碼。
服務(wù)調(diào)用
一個(gè)基于微服務(wù)的應(yīng)用是一個(gè)分布式系統(tǒng),并且必須采用線程間通信的機(jī)制。有兩種線程間通信的方法。一種是采用異步機(jī)制,基于消息的方法。這類的實(shí)現(xiàn)方法有JMS和AMQP。另外的,例如Zeromq屬于服務(wù)間直接通信。還有一種線程間通信采用同步機(jī)制,例如Thrift和HTTP。事實(shí)上一個(gè)系統(tǒng)會(huì)同時(shí)采用同步和異步兩種機(jī)制。由于它的實(shí)現(xiàn)方式有很多種,因此API Gateway就需要支持多種通信方式。
服務(wù)發(fā)現(xiàn)
API Gateway需要知道每一個(gè)微服務(wù)的IP和端口。在傳統(tǒng)應(yīng)用中,你可能會(huì)硬編碼這些地址,但是在現(xiàn)在云基礎(chǔ)的微服務(wù)應(yīng)用中,這將是個(gè)簡(jiǎn)單的問(wèn)題?;A(chǔ)服務(wù)通常會(huì)采用靜態(tài)地址,可以采用操作系統(tǒng)環(huán)境變量來(lái)指定。但是,探測(cè)應(yīng)用服務(wù)的地址就沒(méi)那么容易了。應(yīng)用服務(wù)通常動(dòng)態(tài)分配地址和端口。同樣的,由于擴(kuò)展或者升級(jí),服務(wù)的實(shí)例也會(huì)動(dòng)態(tài)的改變。因此,API Gateway需要采用系統(tǒng)的服務(wù)發(fā)現(xiàn)機(jī)制,要么采用服務(wù)端發(fā)現(xiàn),要么是客戶端發(fā)現(xiàn)。后續(xù)的一篇文章將會(huì)更詳細(xì)的介紹這部分。如果采用客戶端發(fā)現(xiàn)服務(wù),API Gateway必須要去查詢服務(wù)注冊(cè)處,也就是微服務(wù)實(shí)例地址的數(shù)據(jù)庫(kù)。
處理部分失敗
在實(shí)現(xiàn)API Gateway過(guò)程中,另外一個(gè)需要考慮的問(wèn)題就是部分失敗。這個(gè)問(wèn)題發(fā)生在分布式系統(tǒng)中當(dāng)一個(gè)服務(wù)調(diào)用另外一個(gè)服務(wù)超時(shí)或者不可用的情況。API Gateway不應(yīng)該被阻斷并處于無(wú)限期等待下游服務(wù)的狀態(tài)。但是,如何處理這種失敗依賴于特定的場(chǎng)景和具體服務(wù)。例如,如果是在產(chǎn)品詳情頁(yè)的推薦服務(wù)模塊無(wú)響應(yīng),那么API Gateway應(yīng)該返回剩下的其他信息給用戶,因?yàn)檫@些信息也是有用的。推薦部分可以返回空,也可以返回固定的頂部10個(gè)給用戶。但是,如果是產(chǎn)品信息服務(wù)無(wú)響應(yīng),那么API Gateway就應(yīng)該給客戶端返回一個(gè)錯(cuò)誤。
在緩存有效的時(shí)候,API Gateway應(yīng)該能夠返回緩存。例如,由于產(chǎn)品價(jià)格變化并不頻繁,API Gateway在價(jià)格服務(wù)不可用時(shí)應(yīng)該返回緩存中的數(shù)值。這類數(shù)據(jù)可以由API Gateway自身來(lái)緩存,也可以由Redis或Memcached這類外部緩存實(shí)現(xiàn)。通過(guò)返回緩存數(shù)據(jù)或者默認(rèn)數(shù)據(jù),API Gateway來(lái)確保系統(tǒng)錯(cuò)誤不影響到用戶體驗(yàn)。
Netflix Hystrix對(duì)于實(shí)現(xiàn)遠(yuǎn)程服務(wù)調(diào)用代碼來(lái)說(shuō)是一個(gè)非常好用的庫(kù)。Hystrix記錄那些超過(guò)預(yù)設(shè)定的極限值的調(diào)用。它實(shí)現(xiàn)了circuit break模式,使得可以將客戶端從無(wú)響應(yīng)服務(wù)的無(wú)盡等待中停止。如果一個(gè)服務(wù)的錯(cuò)誤率超過(guò)預(yù)設(shè)值,Hystrix將中斷服務(wù),并且在一段時(shí)間內(nèi)所有請(qǐng)求立刻失效。Hystrix可以為請(qǐng)求失敗定義一個(gè)fallback操作,例如讀取緩存或者返回默認(rèn)值。如果你在用JVM,就應(yīng)該考慮使用Hystrix。如果你采用的非JVM環(huán)境,那么應(yīng)該考慮采用類似功能的庫(kù)。
對(duì)于大多數(shù)微服務(wù)基礎(chǔ)的應(yīng)用,實(shí)現(xiàn)一個(gè)API Gateway都是有意義的,它就像是進(jìn)入系統(tǒng)的一個(gè)服務(wù)提供點(diǎn)。API Gateway負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)、請(qǐng)求合成和協(xié)議轉(zhuǎn)換。它提供給應(yīng)用客戶端一個(gè)自定義的API。API Gateway可以通過(guò)返回緩存或者默認(rèn)值的方式來(lái)掩蓋后端服務(wù)的錯(cuò)誤。在本系列的下一篇文章中,我們將討論服務(wù)間的通信問(wèn)題。
原文鏈接:Building Microservices: Using an API Gateway(翻譯:陳杰;審校:楊峰)
===============================================
譯者介紹
陳杰,北京理工大學(xué)計(jì)算機(jī)學(xué)院在讀博士,研究方向是自然語(yǔ)言處理在企業(yè)網(wǎng)絡(luò)信譽(yù)評(píng)價(jià)方面的應(yīng)用,平時(shí)也樂(lè)于去實(shí)現(xiàn)一些突發(fā)的想法。在疲于配置系統(tǒng)環(huán)境時(shí)發(fā)現(xiàn)了Docker,跟大家一起學(xué)習(xí)、使用和研究Docker。
微服務(wù)實(shí)戰(zhàn)(三):深入微服務(wù)架構(gòu)的進(jìn)程間通信
【編者的話】這是采用微服務(wù)架構(gòu)創(chuàng)建自己應(yīng)用系列第三篇文章。第一篇介紹了微服務(wù)架構(gòu)模式,和單體式模式進(jìn)行了比較,并且討論了使用微服務(wù)架構(gòu)的優(yōu)缺點(diǎn)。第二篇描述了采用微服務(wù)架構(gòu)應(yīng)用客戶端之間如何采用API Gateway方式進(jìn)行通信。在這篇文章中,我們將討論系統(tǒng)服務(wù)之間如何通信。
簡(jiǎn)介
在單體式應(yīng)用中,各個(gè)模塊之間的調(diào)用是通過(guò)編程語(yǔ)言級(jí)別的方法或者函數(shù)來(lái)實(shí)現(xiàn)的。但是一個(gè)基于微服務(wù)的分布式應(yīng)用是運(yùn)行在多臺(tái)機(jī)器上的。一般來(lái)說(shuō),每個(gè)服務(wù)實(shí)例都是一個(gè)進(jìn)程。因此,如下圖所示,服務(wù)之間的交互必須通過(guò)進(jìn)程間通信(IPC)來(lái)實(shí)現(xiàn)。
后面我們將會(huì)詳細(xì)介紹IPC技術(shù),現(xiàn)在我們先來(lái)看下設(shè)計(jì)相關(guān)的問(wèn)題。
交互模式
當(dāng)為某一個(gè)服務(wù)選擇IPC時(shí),首先需要考慮服務(wù)之間如何交互??蛻舳撕头?wù)器之間有很多的交互模式,我們可以從兩個(gè)維度進(jìn)行歸類。第一個(gè)維度是一對(duì)一還是一對(duì)多:
??一對(duì)一:每個(gè)客戶端請(qǐng)求有一個(gè)服務(wù)實(shí)例來(lái)響應(yīng)。
??一對(duì)多:每個(gè)客戶端請(qǐng)求有多個(gè)服務(wù)實(shí)例來(lái)響應(yīng)
第二個(gè)維度是這些交互式同步還是異步:
? 同步模式:客戶端請(qǐng)求需要服務(wù)端即時(shí)響應(yīng),甚至可能由于等待而阻塞。
? 異步模式:客戶端請(qǐng)求不會(huì)阻塞進(jìn)程,服務(wù)端的響應(yīng)可以是非即時(shí)的。
下表顯示了不同交互模式:
一對(duì)一的交互模式有以下幾種方式:
? 請(qǐng)求/響應(yīng):一個(gè)客戶端向服務(wù)器端發(fā)起請(qǐng)求,等待響應(yīng)。客戶端期望此響應(yīng)即時(shí)到達(dá)。在一個(gè)基于線程的應(yīng)用中,等待過(guò)程可能造成線程阻塞。
? 通知(也就是常說(shuō)的單向請(qǐng)求):一個(gè)客戶端請(qǐng)求發(fā)送到服務(wù)端,但是并不期望服務(wù)端響應(yīng)。
? 請(qǐng)求/異步響應(yīng):客戶端發(fā)送請(qǐng)求到服務(wù)端,服務(wù)端異步響應(yīng)請(qǐng)求??蛻舳瞬粫?huì)阻塞,而且被設(shè)計(jì)成默認(rèn)響應(yīng)不會(huì)立刻到達(dá)。
一對(duì)多的交互模式有以下幾種方式:
? 發(fā)布/ 訂閱模式:客戶端發(fā)布通知消息,被零個(gè)或者多個(gè)感興趣的服務(wù)消費(fèi)。
? 發(fā)布/異步響應(yīng)模式:客戶端發(fā)布請(qǐng)求消息,然后等待從感興趣服務(wù)發(fā)回的響應(yīng)。
每個(gè)服務(wù)都是以上這些模式的組合,對(duì)某些服務(wù),一個(gè)IPC機(jī)制就足夠了;而對(duì)另外一些服務(wù)則需要多種IPC機(jī)制組合。下圖展示了在一個(gè)打車服務(wù)請(qǐng)求中服務(wù)之間是如何通信的。
上圖中的服務(wù)通信使用了通知、請(qǐng)求/響應(yīng)、發(fā)布/訂閱等方式。例如,乘客通過(guò)移動(dòng)端給『行程管理服務(wù)』發(fā)送通知,希望申請(qǐng)一次出租服務(wù)?!盒谐坦芾矸?wù)』發(fā)送請(qǐng)求/響應(yīng)消息給『乘客服務(wù)』以確認(rèn)乘客賬號(hào)是有效的。緊接著創(chuàng)建此次行程,并用發(fā)布/訂閱交互模式通知其他服務(wù),包括定位可用司機(jī)的調(diào)度服務(wù)。
現(xiàn)在我們了解了交互模式,接下來(lái)我們一起來(lái)看看如何定義API。
定義API
API是服務(wù)端和客戶端之間的契約。不管選擇了什么樣的IPC機(jī)制,重要的是使用某種交互式定義語(yǔ)言(IDL)來(lái)精確定義一個(gè)服務(wù)的API。甚至有一些關(guān)于使用API first的方法(API-first approach)來(lái)定義服務(wù)的很好的理由。在開發(fā)之前,你需要先定義服務(wù)的接口,并與客戶端開發(fā)者詳細(xì)討論確認(rèn)。這樣的討論和設(shè)計(jì)會(huì)大幅度提到API的可用度以及滿意度。
在本文后半部分你將會(huì)看到,API定義實(shí)質(zhì)上依賴于選擇哪種IPC。如果使用消息機(jī)制,API則由消息頻道(channel)和消息類型構(gòu)成;如果選擇使用HTTP機(jī)制,API則由URL和請(qǐng)求、響應(yīng)格式構(gòu)成。后面將會(huì)詳細(xì)描述IDL。
API的演化
服務(wù)端API會(huì)不斷變化。在一個(gè)單體式應(yīng)用中經(jīng)常會(huì)直接修改API,然后更新給所有的調(diào)用者。而在基于微服務(wù)架構(gòu)應(yīng)用中,這很困難,即使只有一個(gè)服務(wù)使用這個(gè)API,不可能強(qiáng)迫用戶跟服務(wù)端保持同步更新。另外,開發(fā)者可能會(huì)嘗試性的部署新版本的服務(wù),這個(gè)時(shí)候,新舊服務(wù)就會(huì)同事運(yùn)行。你需要知道如何處理這些問(wèn)題。
你如何處理API變化,這依賴于這些變化有多大。某些改變是微小的,并且可以和之前版本兼容。比如,你可能只是為某個(gè)請(qǐng)求和響應(yīng)添加了一個(gè)屬性。設(shè)計(jì)客戶端和服務(wù)端時(shí)候應(yīng)該遵循健壯性原理,這很重要??蛻舳耸褂门f版API應(yīng)該也能和新版本一起工作。服務(wù)端仍然提供默認(rèn)響應(yīng)值,客戶端忽略此版本不需要的響應(yīng)。使用IPC機(jī)制和消息格式對(duì)于API演化很有幫助。
但是有時(shí)候,API需要進(jìn)行大規(guī)模的改動(dòng),并且可能與之前版本不兼容。因?yàn)槟悴豢赡軓?qiáng)制讓所有的客戶端立即升級(jí),所以支持老版本客戶端的服務(wù)還需要再運(yùn)行一段時(shí)間。如果你正在使用基于基于HTTP機(jī)制的IPC,例如REST,一種解決方案是把版本號(hào)嵌入到URL中。每個(gè)服務(wù)都可能同時(shí)處理多個(gè)版本的API?;蛘撸憧梢圆渴鸲鄠€(gè)實(shí)例,每個(gè)實(shí)例負(fù)責(zé)處理一個(gè)版本的請(qǐng)求。
處理部分失敗
在上一篇關(guān)于API gateway的文章中,我們了解到分布式系統(tǒng)中部分失敗是普遍存在的問(wèn)題。因?yàn)榭蛻舳撕头?wù)端是都是獨(dú)立的進(jìn)程,一個(gè)服務(wù)端有可能因?yàn)楣收匣蛘呔S護(hù)而停止服務(wù),或者此服務(wù)因?yàn)檫^(guò)載停止或者反應(yīng)很慢。
考慮這篇文章中描述的部分失敗的場(chǎng)景。假設(shè)推薦服務(wù)無(wú)法響應(yīng)請(qǐng)求,那客戶端就會(huì)由于等待響應(yīng)而阻塞,這不僅會(huì)給客戶帶來(lái)很差的體驗(yàn),而且在很多應(yīng)用中還會(huì)占用很多資源,比如線程,以至于到最后由于等待響應(yīng)被阻塞的客戶端越來(lái)越多,線程資源被耗費(fèi)完了。如下圖所示:
為了預(yù)防這種問(wèn)題,設(shè)計(jì)服務(wù)時(shí)候必須要考慮部分失敗的問(wèn)題。
Netfilix提供了一個(gè)比較好的解決方案,具體的應(yīng)對(duì)措施包括:
? 網(wǎng)絡(luò)超時(shí):當(dāng)?shù)却憫?yīng)時(shí),不要無(wú)限期的阻塞,而是采用超時(shí)策略。使用超時(shí)策略可以確保資源不會(huì)無(wú)限期的占用。
? 限制請(qǐng)求的次數(shù):可以為客戶端對(duì)某特定服務(wù)的請(qǐng)求設(shè)置一個(gè)訪問(wèn)上限。如果請(qǐng)求已達(dá)上限,就要立刻終止請(qǐng)求服務(wù)。
?斷路器模式(Circuit Breaker Pattern):記錄成功和失敗請(qǐng)求的數(shù)量。如果失效率超過(guò)一個(gè)閾值,觸發(fā)斷路器使得后續(xù)的請(qǐng)求立刻失敗。如果大量的請(qǐng)求失敗,就可能是這個(gè)服務(wù)不可用,再發(fā)請(qǐng)求也無(wú)意義。在一個(gè)失效期后,客戶端可以再試,如果成功,關(guān)閉此斷路器。
? 提供回滾:當(dāng)一個(gè)請(qǐng)求失敗后可以進(jìn)行回滾邏輯。例如,返回緩存數(shù)據(jù)或者一個(gè)系統(tǒng)默認(rèn)值。
Netflix Hystrix是一個(gè)實(shí)現(xiàn)相關(guān)模式的開源庫(kù)。如果使用JVM,推薦考慮使用Hystrix。而如果使用非JVM環(huán)境,你可以使用類似功能的庫(kù)。
IPC技術(shù)
現(xiàn)在有很多不同的IPC技術(shù)。服務(wù)之間的通信可以使用同步的請(qǐng)求/響應(yīng)模式,比如基于HTTP的REST或者Thrift。另外,也可以選擇異步的、基于消息的通信模式,比如AMQP或者STOMP。除以之外,還有其它的消息格式供選擇,比如JSON和XML,它們都是可讀的,基于文本的消息格式。當(dāng)然,也還有二進(jìn)制格式(效率更高)的,比如Avro和Protocol Buffer。接下來(lái)我們將會(huì)討論異步的IPC模式和同步的IPC模式,首先來(lái)看異步的。
異步的,基于消息通信
當(dāng)使用基于異步交換消息的進(jìn)程通信方式時(shí),一個(gè)客戶端通過(guò)向服務(wù)端發(fā)送消息提交請(qǐng)求。如果服務(wù)端需要回復(fù),則會(huì)發(fā)送另外一個(gè)獨(dú)立的消息給客戶端。因?yàn)橥ㄐ攀钱惒降?,客戶端不?huì)因?yàn)榈却枞?,相反,客戶端理所?dāng)然的認(rèn)為響應(yīng)不會(huì)立刻接收到。
一個(gè)消息由頭部(元數(shù)據(jù)例如發(fā)送方)和消息體構(gòu)成。消息通過(guò)channel發(fā)送,任何數(shù)量的生產(chǎn)者都可以發(fā)送消息到channel,同樣的,任何數(shù)量的消費(fèi)者都可以從渠道中接受數(shù)據(jù)。有兩類channel,點(diǎn)對(duì)點(diǎn)和發(fā)布/訂閱。點(diǎn)對(duì)點(diǎn)channel會(huì)把消息準(zhǔn)確的發(fā)送到某個(gè)從channel讀取消息的消費(fèi)者,服務(wù)端使用點(diǎn)對(duì)點(diǎn)來(lái)實(shí)現(xiàn)之前提到的一對(duì)一交互模式;而發(fā)布/訂閱則把消息投送到所有從channel讀取數(shù)據(jù)的消費(fèi)者,服務(wù)端使用發(fā)布/訂閱channel來(lái)實(shí)現(xiàn)上面提到的一對(duì)多交互模式。
下圖展示了打車軟件如何使用發(fā)布/訂閱:
行程管理服務(wù)在發(fā)布-訂閱channel內(nèi)創(chuàng)建一個(gè)行程消息,并通知調(diào)度服務(wù)有一個(gè)新的行程請(qǐng)求,調(diào)度服務(wù)發(fā)現(xiàn)一個(gè)可用的司機(jī)然后向發(fā)布-訂閱channel寫入司機(jī)建議消息(Driver Proposed message)來(lái)通知其他服務(wù)。
有很多消息系統(tǒng)可以選擇,最好選擇一種支持多編程語(yǔ)言的。一些消息系統(tǒng)支持標(biāo)準(zhǔn)協(xié)議,例如AMQP和STOMP。其他消息系統(tǒng)則使用獨(dú)有的協(xié)議,有大量開源消息系統(tǒng)可選,比如RabbitMQ、Apache Kafka、Apache ActiveMQ和NSQ。它們都支持某種形式的消息和channel,并且都是可靠的、高性能和可擴(kuò)展的;然而,它們的消息模型完全不同。
使用消息機(jī)制有很多優(yōu)點(diǎn):
??解耦客戶端和服務(wù)端:客戶端只需要將消息發(fā)送到正確的channel??蛻舳送耆恍枰私饩唧w的服務(wù)實(shí)例,更不需要一個(gè)發(fā)現(xiàn)機(jī)制來(lái)確定服務(wù)實(shí)例的位置。
??Message Buffering:在一個(gè)同步請(qǐng)求/響應(yīng)協(xié)議中,例如HTTP,所有的客戶端和服務(wù)端必須在交互期間保持可用。而在消息模式中,消息broker將所有寫入channel的消息按照隊(duì)列方式管理,直到被消費(fèi)者處理。也就是說(shuō),在線商店可以接受客戶訂單,即使下單系統(tǒng)很慢或者不可用,只要保持下單消息進(jìn)入隊(duì)列就好了。
? 彈性客戶端-服務(wù)端交互:消息機(jī)制支持以上說(shuō)的所有交互模式。
??直接進(jìn)程間通信:基于RPC機(jī)制,試圖喚醒遠(yuǎn)程服務(wù)看起來(lái)跟喚醒本地服務(wù)一樣。然而,因?yàn)槲锢矶珊筒糠质】赡苄?,他們?shí)際上非常不同。消息使得這些不同非常明確,開發(fā)者不會(huì)出現(xiàn)問(wèn)題。
然而,消息機(jī)制也有自己的缺點(diǎn):
??額外的操作復(fù)雜性:消息系統(tǒng)需要單獨(dú)安裝、配置和部署。消息broker(代理)必須高可用,否則系統(tǒng)可靠性將會(huì)受到影響。
??實(shí)現(xiàn)基于請(qǐng)求/響應(yīng)交互模式的復(fù)雜性:請(qǐng)求/響應(yīng)交互模式需要完成額外的工作。每個(gè)請(qǐng)求消息必須包含一個(gè)回復(fù)渠道ID和相關(guān)ID。服務(wù)端發(fā)送一個(gè)包含相關(guān)ID的響應(yīng)消息到channel中,使用相關(guān)ID來(lái)將響應(yīng)對(duì)應(yīng)到發(fā)出請(qǐng)求的客戶端。也許這個(gè)時(shí)候,使用一個(gè)直接支持請(qǐng)求/響應(yīng)的IPC機(jī)制會(huì)更容易些。
現(xiàn)在我們已經(jīng)了解了基于消息的IPC,接下來(lái)我們來(lái)看看基于請(qǐng)求/響應(yīng)模式的IPC。
同步的,基于請(qǐng)求/響應(yīng)的IPC
當(dāng)使用一個(gè)同步的,基于請(qǐng)求/響應(yīng)的IPC機(jī)制,客戶端向服務(wù)端發(fā)送一個(gè)請(qǐng)求,服務(wù)端處理請(qǐng)求,返回響應(yīng)。一些客戶端會(huì)由于等待服務(wù)端響應(yīng)而被阻塞,而另外一些客戶端也可能使用異步的、基于事件驅(qū)動(dòng)的客戶端代碼(Future或者Rx Observable的封裝)。然而,不像使用消息機(jī)制,客戶端需要響應(yīng)及時(shí)返回。這個(gè)模式中有很多可選的協(xié)議,但最常見的兩個(gè)協(xié)議是REST和Thrift。首先我們來(lái)看下REST。
REST
現(xiàn)在很流行使用RESTful風(fēng)格的API。REST是基于HTTP協(xié)議的。另外,一個(gè)需要理解的比較重要的概念是,REST是一個(gè)資源,一般代表一個(gè)業(yè)務(wù)對(duì)象,比如一個(gè)客戶或者一個(gè)產(chǎn)品,或者一組商業(yè)對(duì)象。REST使用HTTP語(yǔ)法協(xié)議來(lái)修改資源,一般通過(guò)URL來(lái)實(shí)現(xiàn)。舉個(gè)例子,GET請(qǐng)求返回一個(gè)資源的簡(jiǎn)單信息,響應(yīng)格式通常是XML或者JSON對(duì)象格式。POST請(qǐng)求會(huì)創(chuàng)建一個(gè)新資源,PUT請(qǐng)求更新一個(gè)資源。這里引用下REST之父Roy Fielding說(shuō)的:
當(dāng)需要一個(gè)整體的、重視模塊交互可擴(kuò)展性、接口概括性、組件部署獨(dú)立性和減小延遲、提供安全性和封裝性的系統(tǒng)時(shí),REST可以提供這樣一組滿足需求的架構(gòu)。
下圖展示了打車軟件是如何使用REST的。
乘客通過(guò)移動(dòng)端向行程管理服務(wù)的/trips資源提交了一個(gè)POST請(qǐng)求。行程管理服務(wù)收到請(qǐng)求之后,會(huì)發(fā)送一個(gè)GET請(qǐng)求到乘客管理服務(wù)以獲取乘客信息。當(dāng)確認(rèn)乘客信息之后,緊接著會(huì)創(chuàng)建一個(gè)行程,并向移動(dòng)端返回201(譯者注:狀態(tài)碼)響應(yīng)。
很多開發(fā)者都表示他們基于HTTP的API是RESTful的。但是,如同F(xiàn)ielding在他的博客中所說(shuō),這些API可能并不都是RESTful的。Leonard Richardson為REST定義了一個(gè)成熟度模型,具體包含以下4個(gè)層次(摘自IBM):
第一個(gè)層次(Level 0)的 Web 服務(wù)只是使用 HTTP 作為傳輸方式,實(shí)際上只是遠(yuǎn)程方法調(diào)用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。
第二個(gè)層次(Level 1)的 Web 服務(wù)引入了資源的概念。每個(gè)資源有對(duì)應(yīng)的標(biāo)識(shí)符和表達(dá)。
第三個(gè)層次(Level 2)的 Web 服務(wù)使用不同的 HTTP 方法來(lái)進(jìn)行不同的操作,并且使用 HTTP 狀態(tài)碼來(lái)表示不同的結(jié)果。如 HTTP GET 方法來(lái)獲取資源,HTTP DELETE 方法來(lái)刪除資源。
第四個(gè)層次(Level 3)的 Web 服務(wù)使用 HATEOAS。在資源的表達(dá)中包含了鏈接信息。客戶端可以根據(jù)鏈接來(lái)發(fā)現(xiàn)可以執(zhí)行的動(dòng)作。
使用基于HTTP的協(xié)議有如下好處:
? HTTP非常簡(jiǎn)單并且大家都很熟悉。
? 可以使用瀏覽器擴(kuò)展(比如Postman)或者curl之類的命令行來(lái)測(cè)試API。
? 內(nèi)置支持請(qǐng)求/響應(yīng)模式的通信。
? HTTP對(duì)防火墻友好的。
? 不需要中間代理,簡(jiǎn)化了系統(tǒng)架構(gòu)。
不足之處包括:
? 只支持請(qǐng)求/響應(yīng)模式交互。可以使用HTTP通知,但是服務(wù)端必須一直發(fā)送HTTP響應(yīng)才行。
? 因?yàn)榭蛻舳撕头?wù)端直接通信(沒(méi)有代理或者buffer機(jī)制),在交互期間必須都在線。
? 客戶端必須知道每個(gè)服務(wù)實(shí)例的URL。如之前那篇關(guān)于API Gateway的文章所述,這也是個(gè)煩人的問(wèn)題??蛻舳吮仨毷褂梅?wù)實(shí)例發(fā)現(xiàn)機(jī)制。
開發(fā)者社區(qū)最近重新發(fā)現(xiàn)了RESTful API接口定義語(yǔ)言的價(jià)值。于是就有了一些RESTful風(fēng)格的服務(wù)框架,包括RAML和Swagger。一些IDL,例如Swagger允許定義請(qǐng)求和響應(yīng)消息的格式。其它的,例如RAML,需要使用另外的標(biāo)識(shí),例如JSON Schema。對(duì)于描述API,IDL一般都有工具來(lái)定義客戶端和服務(wù)端骨架接口。
Thrift
Apache Thrift是一個(gè)很有趣的REST的替代品。它是Facebook實(shí)現(xiàn)的一種高效的、支持多種編程語(yǔ)言的遠(yuǎn)程服務(wù)調(diào)用的框架。Thrift提供了一個(gè)C風(fēng)格的IDL定義API。使用Thrift編譯器可以生成客戶端和服務(wù)器端代碼框架。編譯器可以生成多種語(yǔ)言的代碼,包括C++、Java、Python、PHP、Ruby, Erlang和Node.js。
Thrift接口包括一個(gè)或者多個(gè)服務(wù)。服務(wù)定義類似于一個(gè)JAVA接口,是一組方法。Thrift方法可以返回響應(yīng),也可以被定義為單向的。返回值的方法其實(shí)就是請(qǐng)求/響應(yīng)類型交互模式的實(shí)現(xiàn)??蛻舳说却憫?yīng),并可能拋出異常。單向方法對(duì)應(yīng)于通知類型的交互模式,服務(wù)端并不返回響應(yīng)。
Thrift支持多種消息格式:JSON、二進(jìn)制和壓縮二進(jìn)制。二進(jìn)制比JSON更高效,因?yàn)槎M(jìn)制解碼更快。同樣原因,壓縮二進(jìn)制格式可以提供更高級(jí)別的壓縮效率。JSON,是易讀的。Thrift也可以在裸TCP和HTTP中間選擇,裸TCP看起來(lái)比HTTP更加有效。然而,HTTP對(duì)防火墻,瀏覽器和人來(lái)說(shuō)更加友好。
消息格式
了解完HTTP和Thrift后,我們來(lái)看下消息格式方面的問(wèn)題。如果使用消息系統(tǒng)或者REST,就可以選擇消息格式。其它的IPC機(jī)制,例如Thrift可能只支持部分消息格式,也許只有一種。無(wú)論哪種方式,我們必須使用一個(gè)跨語(yǔ)言的消息格式,這非常重要。因?yàn)橹覆欢奶炷銜?huì)使用其它語(yǔ)言。
有兩類消息格式:文本和二進(jìn)制。文本格式的例子包括JSON和XML。這種格式的優(yōu)點(diǎn)在于不僅可讀,而且是自描述的。在JSON中,一個(gè)對(duì)象就是一組鍵值對(duì)。類似的,在XML中,屬性是由名字和值構(gòu)成。消費(fèi)者可以從中選擇感興趣的元素而忽略其它部分。同時(shí),小幅度的格式修改可以很容器向后兼容。
XML文檔結(jié)構(gòu)是由XML schema定義的。隨著時(shí)間發(fā)展,開發(fā)者社區(qū)意識(shí)到JSON也需要一個(gè)類似的機(jī)制。一個(gè)選擇是使用JSON Schema,要么是獨(dú)立的,要么是例如Swagger的IDL。
基于文本的消息格式最大的缺點(diǎn)是消息會(huì)變得冗長(zhǎng),特別是XML。因?yàn)橄⑹亲悦枋龅?,所以每個(gè)消息都包含屬性和值。另外一個(gè)缺點(diǎn)是解析文本的負(fù)擔(dān)過(guò)大。所以,你可能需要考慮使用二進(jìn)制格式。
二進(jìn)制的格式也有很多。如果使用的是Thrift RPC,那可以使用二進(jìn)制Thrift。如果選擇消息格式,常用的還包括Protocol Buffers和Apache Avro。它們都提供典型的IDL來(lái)定義消息架構(gòu)。一個(gè)不同點(diǎn)在于Protocol Buffers使用的是加標(biāo)記(tag)的字段,而Avro消費(fèi)者需要知道模式(schema)來(lái)解析消息。因此,使用前者,API更容易演進(jìn)。這篇博客很好的比較了Thrift、Protocol Buffers、Avro三者的區(qū)別。
總結(jié)
微服務(wù)必須使用進(jìn)程間通信機(jī)制來(lái)交互。當(dāng)設(shè)計(jì)服務(wù)的通信模式時(shí),你需要考慮幾個(gè)問(wèn)題:服務(wù)如何交互,每個(gè)服務(wù)如何標(biāo)識(shí)API,如何升級(jí)API,以及如何處理部分失敗。微服務(wù)架構(gòu)有兩類IPC機(jī)制可選,異步消息機(jī)制和同步請(qǐng)求/響應(yīng)機(jī)制。在下一篇文章中,我們將會(huì)討論微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn)問(wèn)題。
原文鏈接:Building Microservices: Inter-Process Communication in a Microservices Architecture(翻譯:楊峰 校對(duì):李穎杰)
微服務(wù)實(shí)戰(zhàn)(四):服務(wù)發(fā)現(xiàn)的可行方案以及實(shí)踐案例
這是關(guān)于使用微服務(wù)架構(gòu)創(chuàng)建應(yīng)用系列的第四篇文章。第一篇介紹了微服務(wù)架構(gòu)的模式,討論了使用微服務(wù)架構(gòu)的優(yōu)缺點(diǎn)。第二和第三篇描述了微服務(wù)架構(gòu)內(nèi)部的通訊機(jī)制。這篇文章中,我們將會(huì)探討服務(wù)發(fā)現(xiàn)相關(guān)問(wèn)題。
設(shè)想一下,我們正在寫代碼使用了提供REST API或者Thrift API的服務(wù),為了完成一次服務(wù)請(qǐng)求,代碼需要知道服務(wù)實(shí)例的網(wǎng)絡(luò)位置(IP地址和端口)。傳統(tǒng)應(yīng)用都運(yùn)行在物理硬件上,服務(wù)實(shí)例的網(wǎng)絡(luò)位置都是相對(duì)固定的。例如,代碼可以從一個(gè)經(jīng)常變更的配置文件中讀取網(wǎng)絡(luò)位置。
而對(duì)于一個(gè)現(xiàn)代的,基于云微服務(wù)的應(yīng)用來(lái)說(shuō),這卻是一個(gè)很麻煩的問(wèn)題。其架構(gòu)如圖所示:
服務(wù)實(shí)例的網(wǎng)絡(luò)位置都是動(dòng)態(tài)分配的,而且因?yàn)閿U(kuò)展、失效和升級(jí)等需求,服務(wù)實(shí)例會(huì)經(jīng)常動(dòng)態(tài)改變,因此,客戶端代碼需要使用一種更加復(fù)雜的服務(wù)發(fā)現(xiàn)機(jī)制。
目前有兩大類服務(wù)發(fā)現(xiàn)模式:客戶端發(fā)現(xiàn)和服務(wù)端發(fā)現(xiàn)。
我們先來(lái)來(lái)討論一下客戶端發(fā)現(xiàn)。
當(dāng)使用客戶端發(fā)現(xiàn)模式時(shí),客戶端負(fù)責(zé)決定相應(yīng)服務(wù)實(shí)例的網(wǎng)絡(luò)位置,并且對(duì)請(qǐng)求實(shí)現(xiàn)負(fù)載均衡??蛻舳藦囊粋€(gè)服務(wù)注冊(cè)服務(wù)中查詢,其中是所有可用服務(wù)實(shí)例的庫(kù)??蛻舳耸褂秘?fù)載均衡算法從多個(gè)服務(wù)實(shí)例中選擇出一個(gè),然后發(fā)出請(qǐng)求。
下圖顯示的是這種模式的架構(gòu)圖:
服務(wù)實(shí)例的網(wǎng)絡(luò)位置是在啟動(dòng)時(shí)注冊(cè)到服務(wù)注冊(cè)表中,并且在服務(wù)終止時(shí)從注冊(cè)表中刪除。服務(wù)實(shí)例注冊(cè)信息一般是使用心跳機(jī)制來(lái)定期刷新的。
Netflix OSS提供了一種非常棒的客戶端發(fā)現(xiàn)模式。Netflix Eureka是一個(gè)服務(wù)注冊(cè)表,為服務(wù)實(shí)例注冊(cè)管理和查詢可用實(shí)例提供了REST API接口。Netflix Ribbon是一種IPC客戶端,與Eureka合同工作實(shí)現(xiàn)對(duì)請(qǐng)求的負(fù)載均衡。我們會(huì)在后面詳細(xì)討論Eureka。
客戶端發(fā)現(xiàn)模式也是優(yōu)缺點(diǎn)分明。這種模式相對(duì)比較直接,而且除了服務(wù)注冊(cè)表,沒(méi)有其它改變的因素。除此之外,因?yàn)榭蛻舳酥揽捎梅?wù)注冊(cè)表信息,因此客戶端可以通過(guò)使用哈希一致性(hashing consistently)變得更加聰明,更加有效的負(fù)載均衡。
而這種模式一個(gè)最大的缺點(diǎn)是需要針對(duì)不同的編程語(yǔ)言注冊(cè)不同的服務(wù),在客戶端需要為每種語(yǔ)言開發(fā)不同的服務(wù)發(fā)現(xiàn)邏輯。
我們分析過(guò)客戶端發(fā)現(xiàn)后,再看看服務(wù)端發(fā)現(xiàn)。
另外一種服務(wù)發(fā)現(xiàn)的模式是服務(wù)端發(fā)現(xiàn)模式(server-side discovery pattern),下圖展現(xiàn)了這種模式的架構(gòu)圖:
客戶端通過(guò)負(fù)載均衡器向某個(gè)服務(wù)提出請(qǐng)求,負(fù)載均衡器向服務(wù)注冊(cè)表發(fā)出請(qǐng)求,將每個(gè)請(qǐng)求轉(zhuǎn)發(fā)往可用的服務(wù)實(shí)例。跟客戶端發(fā)現(xiàn)一樣,服務(wù)實(shí)例在服務(wù)注冊(cè)表中注冊(cè)或者注銷。
AWS Elastic Load Balancer(ELB)是一種服務(wù)端發(fā)現(xiàn)路由的例子,ELB一般用于均衡從網(wǎng)絡(luò)來(lái)的訪問(wèn)流量,也可以使用ELB來(lái)均衡VPC內(nèi)部的流量??蛻舳耸褂肈NS,通過(guò)ELB發(fā)出請(qǐng)求(HTTP或者TCP)。ELB負(fù)載均衡器負(fù)責(zé)在注冊(cè)的EC2實(shí)例或者ECS容器之間均衡負(fù)載,并不存在一個(gè)分離的服務(wù)注冊(cè)表,而EC2實(shí)例和ECS實(shí)例也向ELB注冊(cè)。
HTTP服務(wù)和類似NGINX和NGINX Plus的負(fù)載均衡器都可以作為服務(wù)端發(fā)現(xiàn)均衡器。例如,這篇博文就描述如何使用Consul Template來(lái)動(dòng)態(tài)配置NGINX反向代理。Consul Template是周期性從存放在Consul Template注冊(cè)表中配置數(shù)據(jù)重建配置文件的工具。當(dāng)文件發(fā)生變化時(shí),會(huì)運(yùn)行一個(gè)命令。在如上博客中,Consul Template產(chǎn)生了一個(gè)nginx.conf文件,用于配置反向代理,然后運(yùn)行一個(gè)命令,告訴NGINX重新調(diào)入配置文件。更復(fù)雜的例子可以用HTTP API或者DNS動(dòng)態(tài)重新配置NGINX Plus。
某些部署環(huán)境,例如Kubernetes和Marathon在集群每個(gè)節(jié)點(diǎn)上運(yùn)行一個(gè)代理,此代理作為服務(wù)端發(fā)現(xiàn)負(fù)載均衡器。為了向服務(wù)發(fā)出請(qǐng)求,客戶端使用主機(jī)IP地址和分配的端口通過(guò)代理將請(qǐng)求路由出去。代理將次請(qǐng)求透明的轉(zhuǎn)發(fā)到集群中可用的服務(wù)實(shí)例。
服務(wù)端發(fā)現(xiàn)模式也有優(yōu)缺點(diǎn)。最大的優(yōu)點(diǎn)是客戶端無(wú)需關(guān)注發(fā)現(xiàn)的細(xì)節(jié),客戶端只需要簡(jiǎn)單的向負(fù)載均衡器發(fā)送請(qǐng)求,實(shí)際上減少了編程語(yǔ)言框架需要完成的發(fā)現(xiàn)邏輯。而且,如上說(shuō)所,某些部署環(huán)境免費(fèi)提供以上功能。
這種模式也有缺陷,除非部署環(huán)境提供負(fù)載均衡器,否則負(fù)載均衡器是另外一個(gè)需要配置管理的高可用系統(tǒng)功能。
服務(wù)注冊(cè)表是服務(wù)發(fā)現(xiàn)很重要的部分,它是包含服務(wù)實(shí)例網(wǎng)絡(luò)地址的數(shù)據(jù)庫(kù)。服務(wù)注冊(cè)表需要高可用而且隨時(shí)更新??蛻舳丝梢跃彺鎻姆?wù)注冊(cè)表獲得的網(wǎng)絡(luò)地址。然而,這些信息最終會(huì)變得過(guò)時(shí),客戶端也無(wú)法發(fā)現(xiàn)服務(wù)實(shí)例。因此,服務(wù)注冊(cè)表由若干使用復(fù)制協(xié)議保持同步的服務(wù)器構(gòu)成。
如前所述,Netflix Eureka是一個(gè)服務(wù)注冊(cè)表很好地例子,提供了REST API注冊(cè)和請(qǐng)求服務(wù)實(shí)例。 服務(wù)實(shí)例使用POST請(qǐng)求注冊(cè)網(wǎng)絡(luò)地址,每30秒必須使用PUT方法更新注冊(cè)表,使用HTTP DELETE請(qǐng)求或者實(shí)例超時(shí)來(lái)注銷。可以想見,客戶端可以使用HTTP GET請(qǐng)求接受注冊(cè)服務(wù)實(shí)例信息。
Netflix通過(guò)在每個(gè)AWS EC2域運(yùn)行一個(gè)或者多個(gè)Eureka服務(wù)實(shí)現(xiàn)高可用性,每個(gè)Eureka服務(wù)器都運(yùn)行在擁有彈性IP地址的EC2實(shí)例上。DNS TEXT記錄用于存儲(chǔ)Eureka集群配置,其中存放從可用域到一系列Eureka服務(wù)器網(wǎng)絡(luò)地址的列表。當(dāng)Eureka服務(wù)啟動(dòng)時(shí),向DNS請(qǐng)求接受Eureka集群配置,確認(rèn)同伴位置,給自己分配一個(gè)未被使用的彈性IP地址。
Eureka客戶端—服務(wù)和服務(wù)客戶端—向DNS請(qǐng)求發(fā)現(xiàn)Eureka服務(wù)的網(wǎng)絡(luò)地址,客戶端首選使用同一域內(nèi)的服務(wù)。然而,如果沒(méi)有可用服務(wù),客戶端會(huì)使用另外一個(gè)可用域的Eureka服務(wù)。
另外一些服務(wù)注冊(cè)表例子包括:
etcd– 是一個(gè)高可用,分布式的,一致性的,鍵值表,用于共享配置和服務(wù)發(fā)現(xiàn)。兩個(gè)著名案例包括Kubernetes和Cloud Foundry。
consul– 是一個(gè)用于發(fā)現(xiàn)和配置的服務(wù)。提供了一個(gè)API允許客戶端注冊(cè)和發(fā)現(xiàn)服務(wù)。Consul可以用于健康檢查來(lái)判斷服務(wù)可用性。
Apache ZooKeeper– 是一個(gè)廣泛使用,為分布式應(yīng)用提供高性能整合的服務(wù)。Apache ZooKeeper最初是Hadoop的子項(xiàng)目,現(xiàn)在已經(jīng)變成頂級(jí)項(xiàng)目。
另外,前面強(qiáng)調(diào)過(guò),某些系統(tǒng),例如Kubernetes、Marathon和AWS并沒(méi)有獨(dú)立的服務(wù)注冊(cè)表,對(duì)他們來(lái)說(shuō),服務(wù)注冊(cè)表只是一個(gè)內(nèi)置的功能。
現(xiàn)在我們來(lái)看看服務(wù)注冊(cè)表的概念,看看服務(wù)實(shí)例是如何在注冊(cè)表中注冊(cè)的。
如前所述,服務(wù)實(shí)例必須向注冊(cè)表中注冊(cè)和注銷,如何注冊(cè)和注銷也有一些不同的方式。一種方式是服務(wù)實(shí)例自己注冊(cè),也叫自注冊(cè)模式(self-registration pattern);另外一種方式是為其它系統(tǒng)提供服務(wù)實(shí)例管理的,也叫第三方注冊(cè)模式(third party registration pattern)。我們來(lái)看看自注冊(cè)模式。
當(dāng)使用自注冊(cè)模式時(shí),服務(wù)實(shí)例負(fù)責(zé)在服務(wù)注冊(cè)表中注冊(cè)和注銷。另外,如果需要的話,一個(gè)服務(wù)實(shí)例也要發(fā)送心跳來(lái)保證注冊(cè)信息不會(huì)過(guò)時(shí)。下圖描述了這種架構(gòu):
一個(gè)很好地例子是 Netflix OSS Eureka client。Eureka客戶端負(fù)責(zé)處理服務(wù)實(shí)例的注冊(cè)和注銷。Spring Cloud project,實(shí)現(xiàn)了多種模式,包括服務(wù)發(fā)現(xiàn),使得向Eureka服務(wù)實(shí)例自動(dòng)注冊(cè)時(shí)更容易??梢杂聾EnableEurekaClient注釋Java配置類。
自注冊(cè)模式也有優(yōu)缺點(diǎn)。一個(gè)優(yōu)點(diǎn)是,相對(duì)簡(jiǎn)單,不需要其他系統(tǒng)功能。而一個(gè)主要缺點(diǎn)則是,把服務(wù)實(shí)例跟服務(wù)注冊(cè)表聯(lián)系起來(lái)。必須在每種編程語(yǔ)言和框架內(nèi)部實(shí)現(xiàn)注冊(cè)代碼。
另外一個(gè)方法,不需要連接服務(wù)和注冊(cè)表,則是第三方注冊(cè)模式。
當(dāng)使用第三方注冊(cè)模式時(shí),服務(wù)實(shí)例并不負(fù)責(zé)向服務(wù)注冊(cè)表注冊(cè),而是由另外一個(gè)系統(tǒng)模塊,叫做服務(wù)管理器,負(fù)責(zé)注冊(cè)。服務(wù)管理器通過(guò)查詢部署環(huán)境或訂閱事件來(lái)跟蹤運(yùn)行服務(wù)的改變。當(dāng)管理器發(fā)現(xiàn)一個(gè)新可用服務(wù),會(huì)向注冊(cè)表注冊(cè)此服務(wù)。服務(wù)管理器也負(fù)責(zé)注銷終止的服務(wù)實(shí)例。下圖是這種模式的架構(gòu)圖。
一個(gè)服務(wù)管理器的例子是開源項(xiàng)目Registrator,負(fù)責(zé)自動(dòng)注冊(cè)和注銷被部署為Docker容器的服務(wù)實(shí)例。Reistrator支持多種服務(wù)管理器,包括etcd和Consul。
另外一個(gè)服務(wù)管理器例子是NetflixOSS Prana,主要面向非JVM語(yǔ)言開發(fā)的服務(wù),也稱為附帶應(yīng)用(sidecar application),Prana使用Netflix Eureka注冊(cè)和注銷服務(wù)實(shí)例。
服務(wù)管理器是部署環(huán)境內(nèi)置的模塊。有自動(dòng)擴(kuò)充組創(chuàng)建的EC2實(shí)例可以自向ELB自動(dòng)注冊(cè),Kubernetes服務(wù)自動(dòng)注冊(cè)并且對(duì)發(fā)現(xiàn)服務(wù)可用。
第三方注冊(cè)模式也是優(yōu)缺點(diǎn)都有。主要的優(yōu)點(diǎn)是服務(wù)跟服務(wù)注冊(cè)表是分離的,不需要為每種編程語(yǔ)言和架構(gòu)完成服務(wù)注冊(cè)邏輯,替代的,服務(wù)實(shí)例是通過(guò)一個(gè)集中化管理的服務(wù)進(jìn)行管理的。
一個(gè)缺點(diǎn)是,除非這種服務(wù)被內(nèi)置于部署環(huán)境中,否則也需要配置管理一個(gè)高可用的系統(tǒng)。
在一個(gè)微服務(wù)應(yīng)用中,服務(wù)實(shí)例運(yùn)行環(huán)境是動(dòng)態(tài)變化的。實(shí)例網(wǎng)絡(luò)地址也是動(dòng)態(tài)變化的,因此,客戶端為了訪問(wèn)服務(wù)必須使用服務(wù)發(fā)現(xiàn)機(jī)制。
服務(wù)發(fā)現(xiàn)關(guān)鍵部分是服務(wù)注冊(cè)表,也就是可用服務(wù)實(shí)例的數(shù)據(jù)庫(kù)。服務(wù)注冊(cè)表提供一種注冊(cè)管理API和請(qǐng)求API。服務(wù)實(shí)例使用注冊(cè)管理API來(lái)實(shí)現(xiàn)注冊(cè)和注銷。
請(qǐng)求API用于發(fā)現(xiàn)可用服務(wù)實(shí)例,相對(duì)應(yīng)的,有兩種主要服務(wù)發(fā)現(xiàn)模式:客戶端發(fā)現(xiàn)和服務(wù)端發(fā)現(xiàn)。
在使用客戶端發(fā)現(xiàn)的系統(tǒng)中,客戶端向服務(wù)注冊(cè)表發(fā)起請(qǐng)求,選擇可用實(shí)例,然后發(fā)出服務(wù)請(qǐng)求
而在使用服務(wù)端發(fā)現(xiàn)的系統(tǒng)中,客戶端通過(guò)路由轉(zhuǎn)發(fā)請(qǐng)求,路由器向服務(wù)注冊(cè)表發(fā)出請(qǐng)求,轉(zhuǎn)發(fā)此請(qǐng)求到某個(gè)可用實(shí)例。
服務(wù)實(shí)例注冊(cè)和注銷主要有兩類方式。一種是服務(wù)實(shí)例自動(dòng)注冊(cè)到服務(wù)注冊(cè)表中,也就是自注冊(cè)模式;另外一種則是某個(gè)系統(tǒng)模塊負(fù)責(zé)處理注冊(cè)和注銷,也就是第三方注冊(cè)模式。
在某些部署環(huán)境中,需要配置自己的服務(wù)發(fā)現(xiàn)架構(gòu),例如:Netflix Eureka、etcd或者Apache ZooKeeper。而在另外一些部署環(huán)境中,則自帶了這種功能,例如Kubernetes和Marathon 負(fù)責(zé)處理服務(wù)實(shí)例的注冊(cè)和注銷。他們也在每個(gè)集群節(jié)點(diǎn)上運(yùn)行代理,來(lái)實(shí)現(xiàn)服務(wù)端發(fā)現(xiàn)路由器的功能。
HTTP反向代理和負(fù)載據(jù)衡器(例如NGINX)可以用于服務(wù)發(fā)現(xiàn)負(fù)載均衡器。服務(wù)注冊(cè)表可以將路由信息推送到NGINX,激活一個(gè)實(shí)時(shí)配置更新;例如,可以使用 Consul Template。NGINX Plus 支持額外的動(dòng)態(tài)重新配置機(jī)制,可以使用DNS,將服務(wù)實(shí)例信息從注冊(cè)表中拉下來(lái),并且提供遠(yuǎn)程配置的API。
在未來(lái)的博客中,我們還將深入探討微服務(wù)其它特點(diǎn)。可以注冊(cè)NGINX郵件列表來(lái)獲得最新產(chǎn)品更新提示。
此篇其它博客譯文參見如下地址:
微服務(wù)架構(gòu)的優(yōu)勢(shì)與不足
原文鏈接:Service Discovery in a Microservices Architecture(翻譯:楊峰 校對(duì):宋喻)
微服務(wù)實(shí)踐(五):微服務(wù)的事件驅(qū)動(dòng)數(shù)據(jù)管理
【編者的話】本文是使用微服務(wù)創(chuàng)建應(yīng)用系列的第五篇文章。第一篇文章介紹了微服務(wù)架構(gòu)模式,并且討論了使用微服務(wù)的優(yōu)缺點(diǎn);第二和第三篇描述了微服務(wù)架構(gòu)模塊間通訊的不同方面;第四篇研究了服務(wù)發(fā)現(xiàn)中的問(wèn)題。本篇中,我們從另外一個(gè)角度研究一下微服務(wù)架構(gòu)帶來(lái)的分布式數(shù)據(jù)管理問(wèn)題。
1.1 微服務(wù)和分布式數(shù)據(jù)管理問(wèn)題
單體式應(yīng)用一般都會(huì)有一個(gè)關(guān)系型數(shù)據(jù)庫(kù),由此帶來(lái)的好處是應(yīng)用可以使用 ACID transactions,可以帶來(lái)一些重要的操作特性:
原子性 – 任何改變都是原子性的
一致性 – 數(shù)據(jù)庫(kù)狀態(tài)一直是一致性的
隔離性 – 即使交易并發(fā)執(zhí)行,看起來(lái)也是串行的
Durable – 一旦交易提交了就不可回滾
鑒于以上特性,應(yīng)用可以簡(jiǎn)化為:開始一個(gè)交易,改變(插入,刪除,更新)很多行,然后提交這些交易。
使用關(guān)系型數(shù)據(jù)庫(kù)帶來(lái)另外一個(gè)優(yōu)勢(shì)在于提供SQL(功能強(qiáng)大,可聲明的,表轉(zhuǎn)化的查詢語(yǔ)言)支持。用戶可以非常容易通過(guò)查詢將多個(gè)表的數(shù)據(jù)組合起來(lái),RDBMS查詢調(diào)度器決定最佳實(shí)現(xiàn)方式,用戶不需要擔(dān)心例如如何訪問(wèn)數(shù)據(jù)庫(kù)等底層問(wèn)題。另外,因?yàn)樗袘?yīng)用的數(shù)據(jù)都在一個(gè)數(shù)據(jù)庫(kù)中,很容易去查詢。
然而,對(duì)于微服務(wù)架構(gòu)來(lái)說(shuō),數(shù)據(jù)訪問(wèn)變得非常復(fù)雜,這是因?yàn)閿?shù)據(jù)都是微服務(wù)私有的,唯一可訪問(wèn)的方式就是通過(guò)API。這種打包數(shù)據(jù)訪問(wèn)方式使得微服務(wù)之間松耦合,并且彼此之間獨(dú)立。如果多個(gè)服務(wù)訪問(wèn)同一個(gè)數(shù)據(jù),schema會(huì)更新訪問(wèn)時(shí)間,并在所有服務(wù)之間進(jìn)行協(xié)調(diào)。
更甚于,不同的微服務(wù)經(jīng)常使用不同的數(shù)據(jù)庫(kù)。應(yīng)用會(huì)產(chǎn)生各種不同數(shù)據(jù),關(guān)系型數(shù)據(jù)庫(kù)并不一定是最佳選擇。某些場(chǎng)景,某個(gè)NoSQL數(shù)據(jù)庫(kù)可能提供更方便的數(shù)據(jù)模型,提供更加的性能和可擴(kuò)展性。例如,某個(gè)產(chǎn)生和查詢字符串的應(yīng)用采用例如Elasticsearch的字符搜索引擎。同樣的,某個(gè)產(chǎn)生社交圖片數(shù)據(jù)的應(yīng)用可以采用圖片數(shù)據(jù)庫(kù),例如,Neo4j;因此,基于微服務(wù)的應(yīng)用一般都使用SQL和NoSQL結(jié)合的數(shù)據(jù)庫(kù),也就是被稱為polyglot persistence的方法。
分區(qū)的,polyglot-persistent架構(gòu)用于存儲(chǔ)數(shù)據(jù)有許多優(yōu)勢(shì),包括松耦合服務(wù)和更佳性能和可擴(kuò)展性。然而,隨之而來(lái)的則是分布式數(shù)據(jù)管理帶來(lái)的挑戰(zhàn)。
第一個(gè)挑戰(zhàn)在于如何完成一筆交易的同時(shí)保持多個(gè)服務(wù)之間數(shù)據(jù)一致性。之所以會(huì)有這個(gè)問(wèn)題,我們以一個(gè)在線B2B商店為例,客戶服務(wù)維護(hù)包括客戶的各種信息,例如credit lines。訂單服務(wù)管理訂單,需要驗(yàn)證某個(gè)新訂單與客戶的信用限制沒(méi)有沖突。在單一式應(yīng)用中,訂單服務(wù)只需要使用ACID交易就可以檢查可用信用和創(chuàng)建訂單。
相反的,微服務(wù)架構(gòu)下,訂單和客戶表分別是相對(duì)應(yīng)服務(wù)的私有表,如下圖所示:
訂單服務(wù)不能直接訪問(wèn)客戶表,只能通過(guò)客戶服務(wù)發(fā)布的API來(lái)訪問(wèn)。訂單服務(wù)也可以使用 distributed transactions, 也就是周知的兩階段提交 (2PC)。然而,2PC在現(xiàn)在應(yīng)用中不是可選性。根據(jù)CAP理論,必須在可用性(availability)和ACID一致性(consistency)之間做出選擇,availability一般是更好的選擇。但是,許多現(xiàn)代科技,例如許多NoSQL數(shù)據(jù)庫(kù),并不支持2PC。在服務(wù)和數(shù)據(jù)庫(kù)之間維護(hù)數(shù)據(jù)一致性是非常根本的需求,因此我們需要找其他的方案。
第二個(gè)挑戰(zhàn)是如何完成從多個(gè)服務(wù)中搜索數(shù)據(jù)。例如,設(shè)想應(yīng)用需要顯示客戶和他的訂單。如果訂單服務(wù)提供API來(lái)接受用戶訂單信息,那么用戶可以使用類應(yīng)用型的join操作接收數(shù)據(jù)。應(yīng)用從用戶服務(wù)接受用戶信息,從訂單服務(wù)接受此用戶訂單。假設(shè),訂單服務(wù)只支持通過(guò)私有鍵(key)來(lái)查詢訂單(也許是在使用只支持基于主鍵接受的NoSQL數(shù)據(jù)庫(kù)),此時(shí),沒(méi)有合適的方法來(lái)接收所需數(shù)據(jù)。
對(duì)許多應(yīng)用來(lái)說(shuō),這個(gè)解決方案就是使用事件驅(qū)動(dòng)架構(gòu)(event-driven architecture)。在這種架構(gòu)中,當(dāng)某件重要事情發(fā)生時(shí),微服務(wù)會(huì)發(fā)布一個(gè)事件,例如更新一個(gè)業(yè)務(wù)實(shí)體。當(dāng)訂閱這些事件的微服務(wù)接收此事件時(shí),就可以更新自己的業(yè)務(wù)實(shí)體,也可能會(huì)引發(fā)更多的時(shí)間發(fā)布。
可以使用事件來(lái)實(shí)現(xiàn)跨多服務(wù)的業(yè)務(wù)交易。交易一般由一系列步驟構(gòu)成,每一步驟都由一個(gè)更新業(yè)務(wù)實(shí)體的微服務(wù)和發(fā)布激活下一步驟的事件構(gòu)成。下圖展現(xiàn)如何使用事件驅(qū)動(dòng)方法,在創(chuàng)建訂單時(shí)檢查信用可用度,微服務(wù)通過(guò)消息代理(Messsage Broker)來(lái)交換事件。
訂單服務(wù)創(chuàng)建一個(gè)帶有NEW狀態(tài)的Order (訂單),發(fā)布了一個(gè)“Order Created Event(創(chuàng)建訂單)”的事件。
客戶服務(wù)消費(fèi)Order Created Event事件,為此訂單預(yù)留信用,發(fā)布“Credit Reserved Event(信用預(yù)留)”事件
訂單服務(wù)消費(fèi)Credit Reserved Event,改變訂單的狀態(tài)為OPEN
更復(fù)雜的場(chǎng)景可以引入更多步驟,例如在檢查用戶信用的同時(shí)預(yù)留庫(kù)存等。
考慮到(a)每個(gè)服務(wù)原子性更新數(shù)據(jù)庫(kù)和發(fā)布事件,然后,(b)消息代理確保事件傳遞至少一次,然后可以跨多個(gè)服務(wù)完成業(yè)務(wù)交易(此交易不是ACID交易)。這種模式提供弱確定性,例如最終一致性 eventual consistency。這種交易類型被稱作 BASE model。
亦可以使用事件來(lái)維護(hù)不同微服務(wù)擁有數(shù)據(jù)預(yù)連接(pre-join)的實(shí)現(xiàn)視圖。維護(hù)此視圖的服務(wù)訂閱相關(guān)事件并且更新視圖。例如,客戶訂單視圖更新服務(wù)(維護(hù)客戶訂單視圖)會(huì)訂閱由客戶服務(wù)和訂單服務(wù)發(fā)布的事件。
當(dāng)客戶訂單視圖更新服務(wù)收到客戶或者訂單事件,就會(huì)更新 客戶訂單視圖數(shù)據(jù)集??梢允褂梦臋n數(shù)據(jù)庫(kù)(例如MongoDB)來(lái)實(shí)現(xiàn)客戶訂單視圖,為每個(gè)用戶存儲(chǔ)一個(gè)文檔??蛻粲唵我晥D查詢服務(wù)負(fù)責(zé)響應(yīng)對(duì)客戶以及最近訂單(通過(guò)查詢客戶訂單視圖數(shù)據(jù)集)的查詢。
事件驅(qū)動(dòng)架構(gòu)也是既有優(yōu)點(diǎn)也有缺點(diǎn),此架構(gòu)可以使得交易跨多個(gè)服務(wù)且提供最終一致性,并且可以使應(yīng)用維護(hù)最終視圖;而缺點(diǎn)在于編程模式比ACID交易模式更加復(fù)雜:為了從應(yīng)用層級(jí)失效中恢復(fù),還需要完成補(bǔ)償性交易,例如,如果信用檢查不成功則必須取消訂單;另外,應(yīng)用必須應(yīng)對(duì)不一致的數(shù)據(jù),這是因?yàn)榕R時(shí)(in-flight)交易造成的改變是可見的,另外當(dāng)應(yīng)用讀取未更新的最終視圖時(shí)也會(huì)遇見數(shù)據(jù)不一致問(wèn)題。另外一個(gè)缺點(diǎn)在于訂閱者必須檢測(cè)和忽略冗余事件。
事件驅(qū)動(dòng)架構(gòu)還會(huì)碰到數(shù)據(jù)庫(kù)更新和發(fā)布事件原子性問(wèn)題。例如,訂單服務(wù)必須向ORDER表插入一行,然后發(fā)布Order Created event,這兩個(gè)操作需要原子性。如果更新數(shù)據(jù)庫(kù)后,服務(wù)癱了(crashes)造成事件未能發(fā)布,系統(tǒng)變成不一致狀態(tài)。確保原子操作的標(biāo)準(zhǔn)方式是使用一個(gè)分布式交易,其中包括數(shù)據(jù)庫(kù)和消息代理。然而,基于以上描述的CAP理論,這卻并不是我們想要的。
獲得原子性的一個(gè)方法是對(duì)發(fā)布事件應(yīng)用采用multi-step process involving only local transactions,技巧在于一個(gè)EVENT表,此表在存儲(chǔ)業(yè)務(wù)實(shí)體數(shù)據(jù)庫(kù)中起到消息列表功能。應(yīng)用發(fā)起一個(gè)(本地)數(shù)據(jù)庫(kù)交易,更新業(yè)務(wù)實(shí)體狀態(tài),向EVENT表中插入一個(gè)事件,然后提交此次交易。另外一個(gè)獨(dú)立應(yīng)用進(jìn)程或者線程查詢此EVENT表,向消息代理發(fā)布事件,然后使用本地交易標(biāo)志此事件為已發(fā)布,如下圖所示:
訂單服務(wù)向ORDER表插入一行,然后向EVENT表中插入Order Created event,事件發(fā)布線程或者進(jìn)程查詢EVENT表,請(qǐng)求未發(fā)布事件,發(fā)布他們,然后更新EVENT表標(biāo)志此事件為已發(fā)布。
此方法也是優(yōu)缺點(diǎn)都有。優(yōu)點(diǎn)是可以確保事件發(fā)布不依賴于2PC,應(yīng)用發(fā)布業(yè)務(wù)層級(jí)事件而不需要推斷他們發(fā)生了什么;而缺點(diǎn)在于此方法由于開發(fā)人員必須牢記發(fā)布事件,因此有可能出現(xiàn)錯(cuò)誤。另外此方法對(duì)于某些使用NoSQL數(shù)據(jù)庫(kù)的應(yīng)用是個(gè)挑戰(zhàn),因?yàn)镹oSQL本身交易和查詢能力有限。
此方法因?yàn)閼?yīng)用采用了本地交易更新狀態(tài)和發(fā)布事件而不需要2PC,現(xiàn)在再看看另外一種應(yīng)用簡(jiǎn)單更新狀態(tài)獲得原子性的方法。
另外一種不需要2PC而獲得線程或者進(jìn)程發(fā)布事件原子性的方式就是挖掘數(shù)據(jù)庫(kù)交易或者提交日志。應(yīng)用更新數(shù)據(jù)庫(kù),在數(shù)據(jù)庫(kù)交易日志中產(chǎn)生變化,交易日志挖掘進(jìn)程或者線程讀這些交易日志,將日志發(fā)布給消息代理。如下圖所見:
此方法的例子如LinkedIn Databus 項(xiàng)目,Databus 挖掘Oracle交易日志,根據(jù)變化發(fā)布事件,LinkedIn使用Databus來(lái)保證系統(tǒng)內(nèi)各記錄之間的一致性。
另外的例子如:AWS的 streams mechanism in AWS DynamoDB,是一個(gè)可管理的NoSQL數(shù)據(jù)庫(kù),一個(gè)DynamoDB流是由過(guò)去24小時(shí)對(duì)數(shù)據(jù)庫(kù)表基于時(shí)序的變化(創(chuàng)建,更新和刪除操作),應(yīng)用可以從流中讀取這些變化,然后以事件方式發(fā)布這些變化。
交易日志挖掘也是優(yōu)缺點(diǎn)并存。優(yōu)點(diǎn)是確保每次更新發(fā)布事件不依賴于2PC。交易日志挖掘可以通過(guò)將發(fā)布事件和應(yīng)用業(yè)務(wù)邏輯分離開得到簡(jiǎn)化;而主要缺點(diǎn)在于交易日志對(duì)不同數(shù)據(jù)庫(kù)有不同格式,甚至不同數(shù)據(jù)庫(kù)版本也有不同格式;而且很難從底層交易日志更新記錄轉(zhuǎn)換為高層業(yè)務(wù)事件。
交易日志挖掘方法通過(guò)應(yīng)用直接更新數(shù)據(jù)庫(kù)而不需要2PC介入。下面我們?cè)倏匆环N完全不同的方法:不需要更新只依賴事件的方法。
Event sourcing (事件源)通過(guò)使用根本不同的事件中心方式來(lái)獲得不需2PC的原子性,保證業(yè)務(wù)實(shí)體的一致性。 這種應(yīng)用保存業(yè)務(wù)實(shí)體一系列狀態(tài)改變事件,而不是存儲(chǔ)實(shí)體現(xiàn)在的狀態(tài)。應(yīng)用可以通過(guò)重放事件來(lái)重建實(shí)體現(xiàn)在狀態(tài)。只要業(yè)務(wù)實(shí)體發(fā)生變化,新事件就會(huì)添加到時(shí)間表中。因?yàn)楸4媸录菃我徊僮鳎虼丝隙ㄊ窃有缘摹?/p>
為了理解事件源工作方式,考慮事件實(shí)體作為一個(gè)例子。傳統(tǒng)方式中,每個(gè)訂單映射為ORDER表中一行,例如在ORDER_LINE_ITEM表中。但是對(duì)于事件源方式,訂單服務(wù)以事件狀態(tài)改變方式存儲(chǔ)一個(gè)訂單:創(chuàng)建的,已批準(zhǔn)的,已發(fā)貨的,取消的;每個(gè)事件包括足夠數(shù)據(jù)來(lái)重建訂單狀態(tài)。
事件是長(zhǎng)期保存在事件數(shù)據(jù)庫(kù)中,提供API添加和獲取實(shí)體事件。事件存儲(chǔ)跟之前描述的消息代理類似,提供API來(lái)訂閱事件。事件存儲(chǔ)將事件遞送到所有感興趣的訂閱者,事件存儲(chǔ)是事件驅(qū)動(dòng)微服務(wù)架構(gòu)的基干。
事件源方法有很多優(yōu)點(diǎn):解決了事件驅(qū)動(dòng)架構(gòu)關(guān)鍵問(wèn)題,使得只要有狀態(tài)變化就可以可靠地發(fā)布事件,也就解決了微服務(wù)架構(gòu)中數(shù)據(jù)一致性問(wèn)題。另外,因?yàn)槭浅志没录皇菍?duì)象,也就避免了object relational impedance mismatch problem。
數(shù)據(jù)源方法提供了100%可靠的業(yè)務(wù)實(shí)體變化監(jiān)控日志,使得獲取任何時(shí)點(diǎn)實(shí)體狀態(tài)成為可能。另外,事件源方法可以使得業(yè)務(wù)邏輯可以由事件交換的松耦合業(yè)務(wù)實(shí)體構(gòu)成。這些優(yōu)勢(shì)使得單體應(yīng)用移植到微服務(wù)架構(gòu)變的相對(duì)容易。
事件源方法也有不少缺點(diǎn),因?yàn)椴捎貌煌蛘卟惶煜さ淖兂赡J?,使得重新學(xué)習(xí)不太容易;事件存儲(chǔ)只支持主鍵查詢業(yè)務(wù)實(shí)體,必須使用 Command Query Responsibility Segregation (CQRS) 來(lái)完成查詢業(yè)務(wù),因此,應(yīng)用必須處理最終一致數(shù)據(jù)。
在微服務(wù)架構(gòu)中,每個(gè)微服務(wù)都有自己私有的數(shù)據(jù)集。不同微服務(wù)可能使用不同的SQL或者NoSQL數(shù)據(jù)庫(kù)。盡管數(shù)據(jù)庫(kù)架構(gòu)有很強(qiáng)的優(yōu)勢(shì),但是也面對(duì)數(shù)據(jù)分布式管理的挑戰(zhàn)。第一個(gè)挑戰(zhàn)就是如何在多服務(wù)之間維護(hù)業(yè)務(wù)交易一致性;第二個(gè)挑戰(zhàn)是如何從多服務(wù)環(huán)境中獲取一致性數(shù)據(jù)。
最佳解決辦法是采用事件驅(qū)動(dòng)架構(gòu)。其中碰到的一個(gè)挑戰(zhàn)是如何原子性的更新狀態(tài)和發(fā)布事件。有幾種方法可以解決此問(wèn)題,包括將數(shù)據(jù)庫(kù)視為消息隊(duì)列、交易日志挖掘和事件源。