按照Enterprise Integration Pattern搭建服務(wù)系統(tǒng)
在前一篇文章中,我們已經(jīng)對(duì)Enterprise Integration Pattern中所包含的各個(gè)組成進(jìn)行了簡(jiǎn)單地介紹。限于篇幅(20頁Word以內(nèi)),我并沒有深入地討論各個(gè)組成。但是如果要真正地按照Enterprise Integration Pattern搭建一個(gè)系統(tǒng),僅僅是了解它們實(shí)際上還差得很遠(yuǎn)。因此在本文中,我將會(huì)對(duì)Enterprise Integration Pattern中較容易產(chǎn)生混淆的部分以及一些系統(tǒng)搭建時(shí)常常使用的一些方法進(jìn)行介紹。
尋找最優(yōu)的解決方案
相信您在讀前一篇文章時(shí)就已經(jīng)能感覺到,在使用Enterprise Integration Pattern搭建一個(gè)系統(tǒng)時(shí),我們常常可以通過不同的組成來滿足類似的需求。例如在需要對(duì)消息進(jìn)行轉(zhuǎn)換時(shí),我們常常可以使用Content Enricher或Content Filter等組成來添減內(nèi)容,更可以使用Massaging Mapper等組成來完成類似的功能。那么我們應(yīng)該什么時(shí)候使用Content Enricher或Content Filter,而什么時(shí)候使用Massaging Mapper呢?
其實(shí)答案就存在于過濾器和Endpoint之間的不同。兩者之間的不同主要在于,過濾器是Pipes and Filters模型之中的一個(gè)獨(dú)立的過濾器,而Endpoint則是過濾器中的一個(gè)用來令過濾器內(nèi)部的業(yè)務(wù)邏輯實(shí)現(xiàn)與消息系統(tǒng)關(guān)聯(lián)的組成。例如我們有一個(gè)應(yīng)用提供了一系列與消息系統(tǒng)不兼容的API。此時(shí)我們就需要使用一個(gè)Endpoint將其與消息系統(tǒng)關(guān)聯(lián)。而它們則共同組成了一個(gè)過濾器:

在了解了這點(diǎn)不同之后,相信您就會(huì)明白,為什么Endpoint中所介紹的那些功能與很多用來完成消息路由及轉(zhuǎn)化的過濾器類似了:一類是單獨(dú)存在的過濾器,一類則是過濾器中的組成。相較于使用一個(gè)獨(dú)立的過濾器,Endpoint能夠減少一次在管道中傳輸數(shù)據(jù)的消耗,從而提高了消息的處理速度:

如上圖所示,相較于經(jīng)由過濾器處理,一個(gè)基于Endpoint的具有相同功能的組成可以減少一次消息通過管道進(jìn)行傳遞的過程。因此基于Endpoint的解決方案擁有更好的性能。反過來,由于Endpoint是與其后的業(yè)務(wù)邏輯處于同一個(gè)過濾器中的,因此其無法將消息發(fā)送到其它子系統(tǒng)之中。也就是說,其靈活性有所下降。甚至說,每次對(duì)Endpoint之后的業(yè)務(wù)邏輯的更新同樣需要對(duì)Endpoint進(jìn)行維護(hù),以保證其能正常工作。
這就引出來了一個(gè)話題,在開發(fā)一個(gè)基于Enterprise Integration Pattern的系統(tǒng),最需要考慮的是什么?
通常情況下,這些考慮的因素主要有:性能,靈活度,可維護(hù)性,高可用性。而最終的解決方案則常常是這些因素相互平衡的產(chǎn)物。
性能不必多說。在一個(gè)基于消息的系統(tǒng)中,對(duì)用戶請(qǐng)求的處理最終會(huì)轉(zhuǎn)化為一系列在管道中傳遞的消息。由于消息的傳遞是一個(gè)異步操作,因此對(duì)單個(gè)消息處理時(shí)的性能不會(huì)比同步操作更好。
第一個(gè)使系統(tǒng)具有合適性能的前提就是使子系統(tǒng)擁有合適的粒度。如果子系統(tǒng)的粒度較小,那么對(duì)一個(gè)業(yè)務(wù)邏輯的處理就需要經(jīng)由更多的子系統(tǒng)。這既增加了管道的數(shù)量,又增加了消息在管道中傳輸?shù)拇螖?shù)。除此之外,子系統(tǒng)粒度過細(xì)也會(huì)給消息系統(tǒng)帶來很大的壓力。在前一篇文章中已經(jīng)提到過,管道會(huì)將消息保存起來,因此每個(gè)管道都會(huì)占用一部分內(nèi)存。如果子系統(tǒng)的粒度過細(xì),那么整個(gè)系統(tǒng)就需要更多的管道,對(duì)消息系統(tǒng)所在的服務(wù)器造成更大的壓力,也會(huì)提高系統(tǒng)出錯(cuò)的可能。
一個(gè)擁有較粗粒度的系統(tǒng)最常出現(xiàn)的問題就是系統(tǒng)過載。這時(shí)我們?cè)撛趺醋瞿兀看鸢妇褪菍?duì)其進(jìn)行橫向擴(kuò)展。在《服務(wù)的擴(kuò)展性》一文中我們已經(jīng)提到過,一個(gè)服務(wù)的擴(kuò)展方式分為XYZ軸之分。而在處理系統(tǒng)過載問題上,我們常常需要執(zhí)行X軸擴(kuò)展,有時(shí)也需要進(jìn)行Y軸擴(kuò)展:

X軸擴(kuò)展相對(duì)簡(jiǎn)單:使用多個(gè)服務(wù)實(shí)例對(duì)消息進(jìn)行處理。在這些服務(wù)之前,我們可以使用Message Dispatcher或Competing Consumers這類Endpoint,或者通過Dynamic Router等過濾器來完成對(duì)消息的分發(fā)。只是有時(shí)通過X軸擴(kuò)展并不能完全地解決問題。就像上圖顯示的那樣,對(duì)整個(gè)子系統(tǒng)進(jìn)行擴(kuò)展實(shí)際上會(huì)導(dǎo)致系統(tǒng)的某些組成利用率非常低。在這種時(shí)候,我們就可以將這個(gè)過載的子系統(tǒng)中的各個(gè)組成分離出來,并作為一個(gè)獨(dú)立的基于消息的子系統(tǒng),然后對(duì)其真正形成瓶頸的子系統(tǒng)進(jìn)行擴(kuò)展:

上圖中展示了如何在一個(gè)子系統(tǒng)實(shí)例遇到瓶頸的時(shí)候執(zhí)行Y軸擴(kuò)展。在一開始,該子系統(tǒng)是作為一個(gè)獨(dú)立的系統(tǒng)存在的。在需要處理一個(gè)消息的時(shí)候,消息從其輸入管道流入,并在處理完畢后從其輸出管道流出。但是在該子系統(tǒng)成為整個(gè)系統(tǒng)的瓶頸時(shí),我們就需要將該子系統(tǒng)分割為多個(gè)粒度稍小的子系統(tǒng),并對(duì)其中成為瓶頸的子系統(tǒng)添加新的實(shí)例。這樣,我們就解決了整個(gè)系統(tǒng)中的瓶頸。
但是這樣做的壞處則在于,一個(gè)消息常常需要經(jīng)過更多的管道才能被處理完畢。就以上圖所展示的子系統(tǒng)分割邏輯為例,可以看到,一個(gè)消息在被新系統(tǒng)處理時(shí)將首先需要經(jīng)過綠色的結(jié)點(diǎn),接下來還至少需要經(jīng)過橘紅色的結(jié)點(diǎn)。也就是說,對(duì)消息的處理至少多出了消息流經(jīng)一個(gè)管道的時(shí)間。因此在分割一個(gè)對(duì)消息處理時(shí)間要求較高的系統(tǒng)時(shí),我們常常需要考慮的是,如何使消息經(jīng)過較少的管道。
而在對(duì)消息處理吞吐量的要求超過對(duì)消息處理時(shí)間的要求時(shí),我們則需要盡量地使每個(gè)實(shí)例最大程度地發(fā)揮它的處理能力。這是常識(shí),所以我們不再深入討論。
在考慮系統(tǒng)性能時(shí),我們也常常需要考慮這樣一點(diǎn),那就是有些消息系統(tǒng)提供了創(chuàng)建于內(nèi)存中的管道。在該管道中傳遞消息的性能要比經(jīng)由網(wǎng)絡(luò)傳遞消息的性能高出很多。因此在設(shè)計(jì)基于消息系統(tǒng)的服務(wù)時(shí),我們應(yīng)盡可能地使用這種存在于內(nèi)存中的管道。
而從物理結(jié)構(gòu)上來看,從原有子系統(tǒng)中所分割出來的各個(gè)子系統(tǒng)實(shí)例也需要和消息系統(tǒng)服務(wù)所在的實(shí)例進(jìn)行交互。我們當(dāng)然可以將管道直接添加到原有的消息系統(tǒng)服務(wù)上,而另一個(gè)較為常見的方法則是創(chuàng)建一個(gè)新的消息系統(tǒng)服務(wù)。這可以帶來非常多的好處:使消息系統(tǒng)服務(wù)能夠承擔(dān)更多的負(fù)載,使得整個(gè)系統(tǒng)的物理拓?fù)溥壿嬜兊酶鼮榍逦梢栽诓煌南⒎?wù)上使用不同的安全配置,例如將其設(shè)置為只接受從中心消息服務(wù)所發(fā)送的消息,進(jìn)而提高整個(gè)系統(tǒng)的安全性:

而一個(gè)與性能常常有沖突的地方就是系統(tǒng)的靈活度。我們可以回想一下前一篇文章中對(duì)Content-Based Router及Dynamic Router的講解。兩者之間的不同主要在于Dynamic Router可以令一個(gè)過濾器注冊(cè)自身所能接收消息的條件,從而能夠動(dòng)態(tài)地加入或離開系統(tǒng)。而這也并非沒有代價(jià)。為了提供這種靈活度,我們需要添加額外的管道,從而為消息服務(wù)帶來了更多的負(fù)載。
但是過多的靈活性反而也會(huì)導(dǎo)致問題的大量出現(xiàn)。例如對(duì)于某些服務(wù),我們可以假設(shè)其可能會(huì)由于業(yè)務(wù)的快速增長而達(dá)到系統(tǒng)的瓶頸,因此為其設(shè)計(jì)較強(qiáng)的靈活性是有必要的。而對(duì)于某些系統(tǒng),我們常常不應(yīng)該設(shè)計(jì)有太大的靈活性。例如在當(dāng)前需求僅僅是針對(duì)用戶類型來提供相應(yīng)推薦的推薦系統(tǒng)而言,當(dāng)天的推薦實(shí)際上是固定的一系列推薦項(xiàng)的組合,因此也不存在什么需要根據(jù)用戶偏好動(dòng)態(tài)計(jì)算推薦項(xiàng)的功能。對(duì)于這樣的一個(gè)服務(wù),只要需求沒有發(fā)生變化,整個(gè)系統(tǒng)的計(jì)算負(fù)載也不會(huì)高,因此也不必為它的處理能力擴(kuò)展留下太多的靈活性。
而在需求變化時(shí),例如我們現(xiàn)在需要根據(jù)用戶的瀏覽記錄來推薦物品,那么我們就需要考慮系統(tǒng)的靈活性了。因?yàn)榇藭r(shí)推薦系統(tǒng)的計(jì)算結(jié)果可能會(huì)隨著用戶的當(dāng)前瀏覽而隨時(shí)發(fā)生變化。隨著用戶的快速增多,這種負(fù)載將會(huì)越來越重,從而造成子系統(tǒng)的過載。
況且,過多的靈活性也會(huì)導(dǎo)致可維護(hù)性變得更為困難。還是以Content-Based Router以及Dynamic Router為例。Dynamic Router之所以出現(xiàn),就是因?yàn)楫?dāng)其中一個(gè)參與消息處理的子系統(tǒng)發(fā)生變化時(shí),我們還需要更改Content-Based Router。這會(huì)導(dǎo)致Content-Based Router之后的整個(gè)子系統(tǒng)暫時(shí)不可用。
這實(shí)際上就對(duì)參與消息系統(tǒng)中的各個(gè)組成之間的耦合性提出了要求。如果對(duì)某個(gè)組成的修改會(huì)導(dǎo)致我們更改其它一些組成,那么它們之間就是耦合的。對(duì)于不常變動(dòng)的組成關(guān)系,這種耦合是正常的,而對(duì)于常常會(huì)發(fā)生變化的組成,尤其是在為整個(gè)系統(tǒng)設(shè)計(jì)高可用性,熱插拔功能時(shí),這些耦合就是相對(duì)致命的。
可以這么說,對(duì)于一個(gè)重要的系統(tǒng),如何讓它在發(fā)生變化時(shí)不需要停止服務(wù)常常是其所最為看重的。高可用性是其中的一種需求,在維護(hù)時(shí)不需要停止服務(wù)也是一種非常重要的需求。因此在設(shè)計(jì)一個(gè)系統(tǒng)時(shí),我們常常考慮的是:哪里可能會(huì)經(jīng)常發(fā)生變化?發(fā)生變化之后我們需要更改哪些組成?如果系統(tǒng)的某個(gè)相關(guān)組成失效,整個(gè)系統(tǒng)是否能夠繼續(xù)正常提供服務(wù)?除此之外,是否有不必要的消息傳遞?
選擇合適的組成
好。上一節(jié)我們已經(jīng)介紹了在設(shè)計(jì)一個(gè)基于消息的系統(tǒng)時(shí)所需要考慮的各種因素。而在本節(jié)中,我們將對(duì)Enterprise Integration Pattern中所介紹的一些組成進(jìn)行分析,從而使您更清楚地了解這些組成之間的優(yōu)點(diǎn)和缺點(diǎn),并最終能夠正確地使用它們。
就像Open-Close原則一樣,我們?cè)诨贓nterprise Integration Pattern設(shè)計(jì)一個(gè)系統(tǒng)的時(shí)候也需要考慮這些系統(tǒng)中各個(gè)子系統(tǒng)之間的變與不變。變?cè)谶@里主要分為兩種:系統(tǒng)中的各個(gè)子系統(tǒng)之間的關(guān)系發(fā)生變化,以及路由過程中消息自身的路由方式發(fā)生變化。搞清系統(tǒng)中的變與不變能夠提供較高的靈活性和可維護(hù)性。但是由于靈活性和可維護(hù)性常常需要引入一系列額外的組成,因此其常常會(huì)影響整個(gè)系統(tǒng)的性能。因此除了需要考慮整個(gè)系統(tǒng)的性能之外,我們還需要考慮各個(gè)組成的性能。在這兩種思考方式下,消息系統(tǒng)各個(gè)組成之間的異同就會(huì)顯得十分清晰明了。
這些額外引入的組成常常意味著性能的下降以及維護(hù)成本的增加。例如就以使用一個(gè)Content-Based Router完成消息的分發(fā)這種最為簡(jiǎn)單的情況為例,它的好處就是能讓我們把所有的路由邏輯都集中在一個(gè)組成中完成。這樣只要消息中的數(shù)據(jù)發(fā)生了變化,或者有新的子系統(tǒng)添加到路由邏輯之中,我們只需要更改這些路由邏輯即可。這便是集中管理信息的好處,或者是SRP(笑,Single Responsibility Principle,思想類似,隨便扯扯)。但是反過來,如果一個(gè)子系統(tǒng)所能接收的消息類型發(fā)生了變化,那么我們就需要同時(shí)修改該子系統(tǒng)以及相應(yīng)的路由器。而這就是一種并不受待見的耦合,尤其是在一個(gè)接收端會(huì)經(jīng)常發(fā)生變化的系統(tǒng)中,這種變化所帶來的困擾遠(yuǎn)大于我們集中管理信息所帶來的好處。
反過來,很多消息系統(tǒng)也同樣允許我們創(chuàng)建自定義的各個(gè)組成,例如自定義的路由器,自定義的消息轉(zhuǎn)換邏輯等。在這些情況下,我們也可以通過一系列業(yè)界常用的思想來解決這些問題。
就讓我們從最先介紹的Content-Based Router說起。一個(gè)Content-Based Router會(huì)根據(jù)消息中所包含的信息來決定到底由哪個(gè)子系統(tǒng)對(duì)該消息進(jìn)行處理。這也就是說,Content-Based Router知道到底有哪些子系統(tǒng),同時(shí)它還知道如何去分析這些消息。那么一旦這些處理消息的子系統(tǒng)的可見性發(fā)生了變化,或者消息中所包含的信息發(fā)生了變化,那么我們就需要對(duì)Content-Based Router內(nèi)的邏輯進(jìn)行更改。
而為了避免這些維護(hù)上的問題,Enterprise Integration Pattern提出了Dynamic Router。其允許各個(gè)接收消息的子系統(tǒng)向其注冊(cè)處理問題的條件。那是不是Content-Based Router就沒有任何價(jià)值了呢?不是的。相對(duì)于Dynamic Router,Content-Based Router是一個(gè)更輕量級(jí)的解決方案。因此在篩選條件不會(huì)發(fā)生變化而且參與消息分發(fā)的子系統(tǒng)不會(huì)發(fā)生變化的情況下,其反而是最佳的解決方案。除此之外,如何避免參與分發(fā)的各個(gè)子系統(tǒng)向Dynamic Router所注冊(cè)的條件不會(huì)發(fā)生彼此相互重疊的情況也是一個(gè)需要討論的問題。這也是Dynamic Router的這種靈活性所帶來的副作用。
你仔細(xì)想一想就會(huì)發(fā)現(xiàn),實(shí)際上這就是一個(gè)依賴注入。只不過我們不是在具體編程過程中對(duì)其進(jìn)行使用,而是在整個(gè)系統(tǒng)設(shè)計(jì)時(shí)候完成的。所注入的,則是Dynamic Router所需要的作為消息分發(fā)依據(jù)的邏輯。同樣的,Service Locator也會(huì)幫我們解決一系列耦合的問題。在Enterprise Integration Pattern中,典型的借鑒Service Locator的組成似乎并不多,但是在實(shí)際使用中,我們也的確可以通過這樣設(shè)計(jì)系統(tǒng)來完成各個(gè)系統(tǒng)之間的解耦。
我說的意思實(shí)際是,雖然說不同層次上所常用的各種方法會(huì)存在著一些不同,例如我們無法像創(chuàng)建一個(gè)派生類一樣對(duì)子系統(tǒng)進(jìn)行派生,但是很多時(shí)候思想是通用的。
OK,這段扯得有點(diǎn)遠(yuǎn)。我們拉回到如何區(qū)分并合理地使用各個(gè)組成這樣一個(gè)話題中。我們?cè)谇懊嬉呀?jīng)講解過什么時(shí)候使用過濾器,什么時(shí)候使用Endpoint。因此在這里我們將會(huì)把精力主要集中在負(fù)責(zé)路由的各個(gè)過濾器上。因?yàn)檫@常常是很多人產(chǎn)生疑惑的地方。
在Enterprise Integration Pattern一書中列入了如下的一個(gè)用來決定一個(gè)系統(tǒng)中所需要使用的路由器的判斷邏輯圖:

但是我個(gè)人認(rèn)為這個(gè)圖是根據(jù)各個(gè)路由過濾器的特性來去分類的。而在我實(shí)際決策過程中,我更趨向于根據(jù)業(yè)務(wù)邏輯以及消息處理本身的需求來決定到底使用哪個(gè)路由器。該判斷邏輯如下所示:

因?yàn)槲乙恢庇X得,對(duì)一個(gè)消息如何進(jìn)行處理才是與業(yè)務(wù)邏輯關(guān)聯(lián)最密切的。業(yè)務(wù)邏輯以及某些非功能性需求決定了到底我們需要什么樣的路由邏輯。而且在上圖中,我也把Dynamic Router包含進(jìn)了Content-Based Router中了。因?yàn)閷?shí)際上,Dynamic Router就是一種特殊的Content-Based Router。當(dāng)然,仁者見仁,智者見智。不是說原書中的決策邏輯不好,而僅僅是將我所使用的決策邏輯介紹給大家。
而對(duì)于用來進(jìn)行消息轉(zhuǎn)化的Transformer以及各個(gè)Endpoint,由于我覺得它們實(shí)際上還是很容易區(qū)分的,因此在本文中就不再做細(xì)致的講解了。
管理基于消息的系統(tǒng)
在前面的講解中,我們只介紹了應(yīng)該如何通過Enterprise Integration Pattern所提及的各種組成搭建一個(gè)系統(tǒng)。但是除了業(yè)務(wù)邏輯之外,我們還需要令我們的系統(tǒng)滿足一定的非功能性需求,例如高可用性,可測(cè)試性等。因此在搭建了一個(gè)系統(tǒng)之后,我們還需要做一系列的工作,才能讓我們的系統(tǒng)穩(wěn)定持續(xù)地提供服務(wù)。
但是對(duì)這些非功能性需求的保證則沒有那么簡(jiǎn)單。例如,在一個(gè)基于消息的系統(tǒng)中,消息的生產(chǎn)者和消費(fèi)者并不知道彼此,同時(shí)對(duì)消息的傳送常常是一個(gè)異步的調(diào)用,其只對(duì)消息的可靠傳遞進(jìn)行了保證,卻沒有對(duì)消息的傳遞時(shí)間進(jìn)行保證。因此如何滿足這些非功能性需求則是更為困難的一件事。
Enterprise Integration Pattern一書中提供了一系列用以提供這些非功能性需求的解決方案。在本節(jié)中,我們就將對(duì)這些解決方案進(jìn)行簡(jiǎn)單地介紹。
首先要介紹的就是Control Bus。在該方案中,Control Bus將使用獨(dú)立的管道與系統(tǒng)中的各個(gè)子系統(tǒng)關(guān)聯(lián),以動(dòng)態(tài)地監(jiān)控各組成的運(yùn)行狀態(tài),如子系統(tǒng)是否正常工作,與其運(yùn)行相關(guān)的統(tǒng)計(jì)數(shù)據(jù),其是否過載,消息的處理是否有較高的延遲等。甚至在監(jiān)控到了某些異常狀態(tài)之后,其還需要通過這些管道向這些子系統(tǒng)發(fā)送消息,以更改這些子系統(tǒng)的配置:

那么我們應(yīng)該如何通過這些消息來判斷一個(gè)子系統(tǒng)是否正常工作呢?簡(jiǎn)單地說,我們可以令子系統(tǒng)向管道中送入一系列心跳消息的方式來解決。這種心跳消息可能僅僅是一個(gè)簡(jiǎn)單的通知消息,更可以在消息中包含子系統(tǒng)當(dāng)前的狀態(tài)信息,如處理了多少消息,每個(gè)消息的處理時(shí)間,整個(gè)系統(tǒng)的狀態(tài)等。
但是這些信息僅僅用來描述子系統(tǒng)的當(dāng)前運(yùn)行狀態(tài)。我們?cè)趺磁袛嘧酉到y(tǒng)的業(yè)務(wù)邏輯是否正常執(zhí)行呢?此時(shí)我們就需要使用Enterprise Integration Pattern中所介紹的Test Message方案:

從上圖中可以看到,Test Message主要包含了四個(gè)組成:Generator將首先生成測(cè)試消息,接下來,該測(cè)試消息將會(huì)通過Injector與實(shí)際的業(yè)務(wù)消息發(fā)送到子系統(tǒng)中。在子系統(tǒng)處理完畢之后,Separator將會(huì)把這些測(cè)試消息對(duì)應(yīng)的處理結(jié)果分離,并將這些處理結(jié)果發(fā)送給Verifier進(jìn)行驗(yàn)證。而這些驗(yàn)證的結(jié)果將被發(fā)送到Control Bus中,以方便Control Bus管理這些子系統(tǒng)。
好了,現(xiàn)在我們已經(jīng)知道了如何探測(cè)一個(gè)子系統(tǒng)是否在正常工作。下一步則是為我們追蹤及調(diào)試系統(tǒng)作準(zhǔn)備。為了能夠完成這些功能,我們首先需要能夠偵聽在兩個(gè)子系統(tǒng)之間所傳遞的消息,才能通過這些偵聽到的消息來進(jìn)行調(diào)試。當(dāng)然,在一個(gè)Publish-Subscribe管道上偵聽消息是非常簡(jiǎn)單的:我們只需要偵聽該管道上的消息即可。但是由于Point-to-Point管道將只能對(duì)消息進(jìn)行點(diǎn)對(duì)點(diǎn)傳輸,因此我們不能簡(jiǎn)單地對(duì)該管道上的消息進(jìn)行偵聽。為了解決這個(gè)問題,Enterprise Integration Pattern則提出了Wire Tap方案。該方案會(huì)將Point-to-Point管道的一端連接到Wire Tap上,然后由其向目標(biāo)子系統(tǒng)以及偵聽方轉(zhuǎn)發(fā)該消息:

好的,現(xiàn)在我們能夠偵聽這些消息了,下一步則是找到一個(gè)地方把它們存起來。該功能是通過Message Store來完成的:

從上圖中可以看到,Message Store會(huì)要求各個(gè)子系統(tǒng)在向輸出管道放置消息時(shí)也向消息的存儲(chǔ)發(fā)送一個(gè)相同的消息,從而完成對(duì)這些消息的持久化。但是我們?cè)趺床拍苤酪粋€(gè)消息到底是如何在系統(tǒng)中流動(dòng)的呢?答案是通過Message History來記錄消息所經(jīng)過的各個(gè)子系統(tǒng):

而為了重現(xiàn)并調(diào)試某些出錯(cuò)的情況,我們則需要讓某個(gè)消息能夠經(jīng)過一系列特殊的子系統(tǒng),從而允許軟件開發(fā)人員對(duì)出錯(cuò)的情況進(jìn)行調(diào)試。此時(shí)我們就需要使用Detour方案。該方案會(huì)使用一個(gè)Context-Based Router判斷某個(gè)消息是否滿足特殊條件,如果是,那么將其傳遞給特定的輸出管道:

但是這里有一個(gè)問題,那就是我們更改了消息的路由路徑。這明顯會(huì)影響Request-Reply類型的消息的執(zhí)行。為了解決這個(gè)問題,我們需要使用一個(gè)Smart Proxy。該組成能夠緩存原消息的Return Address。這樣當(dāng)一個(gè)消息經(jīng)過該組成時(shí),其將首先緩存該消息的Return Address,并使用自己的響應(yīng)輸入管道地址替換消息中的返回地址。當(dāng)消息從該管道返回時(shí),Smart Proxy則會(huì)找到原有的Return Address并將消息送回。
至于Enterprise Integration Pattern中所提到的最后一個(gè)組成Channel Purger則非常容易理解。由于消息是在消息系統(tǒng)中緩存的。當(dāng)我們重新啟動(dòng)某個(gè)子系統(tǒng),或者對(duì)某個(gè)子系統(tǒng)進(jìn)行調(diào)試時(shí),其管道中所存留的消息將會(huì)明顯地影響我們的調(diào)試。Channel Purger則會(huì)幫助我們解決這個(gè)問題:其會(huì)將管道中的不需要的消息移除。
適當(dāng)?shù)厥褂?/strong>EIP
最后一節(jié),我們則主要用來討論您應(yīng)該如何在合適的時(shí)機(jī)以合適的方式使用Enterprise Integration Pattern所提供的各種功能。
首先要明白的就是什么時(shí)候使用Enterprise Integration Pattern。試想一下,如果一個(gè)系統(tǒng)對(duì)一個(gè)用戶請(qǐng)求的處理需要5秒鐘,那么一個(gè)瀏覽器用戶需要很長時(shí)間才能完成對(duì)頁面上所有數(shù)據(jù)的加載。對(duì)于不同的任務(wù),用戶對(duì)該行為的忍受能力其實(shí)并不相同。例如如果用戶加載一個(gè)服務(wù)的首頁都需要2分鐘,那么他極有可能放棄使用該服務(wù)。但是如果一個(gè)功能是在后臺(tái)做了非常耗時(shí)的操作,如部署虛機(jī)并在其上安裝運(yùn)行服務(wù)所需要的軟件,那么對(duì)該請(qǐng)求的處理耗時(shí)10分鐘都不足為過。此時(shí)我們只需要提供給用戶一個(gè)界面并定時(shí)地刷新任務(wù)的執(zhí)行狀態(tài),以通知我們的系統(tǒng)正在工作既可。
因此,一個(gè)原本就需要較長時(shí)間耗時(shí)的,或者是至少用戶能夠理解為較為耗時(shí)的功能,才能使用Enterprise Integration Pattern對(duì)其進(jìn)行組織。很多直接面向用戶的功能,如電子商務(wù),博客,很少直接使用到這些需要長時(shí)間耗時(shí)的操作,因此使用Enterprise Integration Pattern來組織這些功能只會(huì)讓您的服務(wù)質(zhì)量變得更差。
那么我們應(yīng)該在什么時(shí)候使用Enterprise Integration Pattern呢?答案實(shí)際上就存在于Enterpriese這一個(gè)詞上。很多企業(yè)級(jí)應(yīng)用常常包含一系列非常耗時(shí)的操作。就以現(xiàn)在最流行的云來說吧。我做的產(chǎn)品就是一個(gè)云管理軟件。這個(gè)軟件能讓用戶通過簡(jiǎn)單地拖拽就能定義其在特定云上所需要部署的服務(wù)。接下來,用戶只要點(diǎn)擊一下部署,在幾十分鐘后,該應(yīng)用就將被部署完畢。
讓我們想一想這個(gè)云管理軟件在部署時(shí)做了哪些事情呢?從Amazon上請(qǐng)求資源,對(duì)資源進(jìn)行配置,在這些資源上部署服務(wù)所需要的各個(gè)軟件,配置這些軟件,并最終啟動(dòng)服務(wù)。可以想象到的是,這里面的每一步都是一個(gè)較為耗時(shí)的操作。而且它是一個(gè)非常典型的按照Pipes and Filters模型組織的業(yè)務(wù)邏輯:

而為了能讓用戶能夠知道我們的應(yīng)用正在正常工作,我們則會(huì)將當(dāng)前部署任務(wù)的狀態(tài)回填到數(shù)據(jù)庫中。這樣用戶在請(qǐng)求查看當(dāng)前任務(wù)的運(yùn)行狀態(tài)時(shí),我們只需要從數(shù)據(jù)庫中將該狀態(tài)讀出返回既可。因此,雖然我們的部署服務(wù)所需要消耗的時(shí)間較長,但是用戶在請(qǐng)求查看時(shí),我們就能非常快速地返回,不是么?
其實(shí)這是業(yè)內(nèi)非常常見的一種對(duì)耗時(shí)任務(wù)的一種展示方法。只是由于這可能涉及到我們公司產(chǎn)品的內(nèi)部實(shí)現(xiàn),因此為了避免一些不必要的麻煩,我會(huì)找機(jī)會(huì)在介紹其它公司的產(chǎn)品,例如Amazon的CloudFormation,Beanstalk或OpsWorks等再對(duì)它的內(nèi)部執(zhí)行邏輯進(jìn)行講解。
而且從云這個(gè)領(lǐng)域來看,其實(shí)現(xiàn)在對(duì)云服務(wù)提供Enterprise Integration Pattern的原生支持這一要求的呼聲也是很高的。這也就是所謂的Cloud Orchestration的一個(gè)重要的組成部分。當(dāng)然啊,這玩藝挺大也挺虛的。我盡量把它們一步步細(xì)化地講解掉,畢竟我這一系列和Web Service的文章都是一步步地向著這個(gè)目標(biāo)前進(jìn)的。從前面的負(fù)載平衡,后面的擴(kuò)展性,然后還有以后要講的高可用性(尤其是基于云的),Amazon云所提供的功能等,我都會(huì)抽出時(shí)間寫成博客。
轉(zhuǎn)載請(qǐng)注明原文地址并標(biāo)明轉(zhuǎn)載:http://www.rzrgm.cn/loveis715/p/5185353.html
商業(yè)轉(zhuǎn)載請(qǐng)事先與我聯(lián)系:silverfox715@sina.com
公眾號(hào)一定幫忙別標(biāo)成原創(chuàng),因?yàn)閰f(xié)調(diào)起來太麻煩了。。。

浙公網(wǎng)安備 33010602011771號(hào)