微服务架构模式简介
在2014年,Sam Newman,Martin Fowler在ThoughtWorks的一位同事,出版了一本新書《Building Microservices》。該書描述了如何按照Microservice架構(gòu)模式設(shè)計(jì)及搭建一個具有良好擴(kuò)展性并可持續(xù)開發(fā)的系統(tǒng)。除此之外,該書還將基于該模式的系統(tǒng)演化流程與Continuous Delivery等當(dāng)前甚為流行的開發(fā)流程結(jié)合在了一起,使得Microservice架構(gòu)模式看起來非常具有吸引力。基于這些原因,該架構(gòu)模式迅速被業(yè)界所熟知,并在多個產(chǎn)品中被嘗試著使用。這其中就包含了我們公司的產(chǎn)品vRA。
在這一年多的時間里,我們不但真正地體會到了Microservice所具有的一系列優(yōu)點(diǎn),也犯過一系列錯誤。因此在這篇文章里,我會對Microservice架構(gòu)模式進(jìn)行簡單地介紹,并將我們所得到的經(jīng)驗(yàn)和教訓(xùn)介紹給大家。
Monolith
網(wǎng)上對Microservice進(jìn)行介紹的文章常常以Monolith作為開頭,我也不會例外。原因是,知道了Monolith的不便之后才能更容易地理解Microservice架構(gòu)模式所具有的各種優(yōu)點(diǎn)。
首先請回想一下我們所開發(fā)的服務(wù)是什么樣子的。通常情況下,這個服務(wù)所對應(yīng)的代碼由多個項(xiàng)目所組成,各個項(xiàng)目會根據(jù)自身所提供功能的不同具有一個明確的邊界。在編譯時,這些項(xiàng)目將被打包成為一個個JAR包,并最終合并在一起形成一個WAR包。接下來,我們需要將該WAR包上傳到Web容器中,解壓該WAR包,并重新啟動服務(wù)器。在執(zhí)行完這一系列操作之后,我們對服務(wù)的編譯及部署就已經(jīng)完成了:
這種將所有的代碼及功能都包含在一個WAR包中的項(xiàng)目組織方式被稱為Monolith。在項(xiàng)目較小的情況下,這種代碼組織方式還是可以接受的:更改完代碼后,軟件開發(fā)人員可以趁著編譯器編譯代碼的時候沖杯咖啡,并在回到座位后花費(fèi)一分鐘部署剛剛編譯出來的WAR包以便測試自己剛剛所做的更改。但隨著項(xiàng)目的逐漸變大,整個開發(fā)流程的時間也會變得很長:即使在僅僅更改了一行代碼的情況下,軟件開發(fā)人員需要花費(fèi)幾十分鐘甚至超過一個小時的時間對所有代碼進(jìn)行編譯,并接下來花費(fèi)大量的時間重新部署剛剛生成的產(chǎn)品,以驗(yàn)證自己的更改是否正確。
如果應(yīng)用的部署非常麻煩,那么為了對自己的更改進(jìn)行測試,軟件開發(fā)人員還需要在部署前進(jìn)行大量的環(huán)境設(shè)置,進(jìn)而使得軟件開發(fā)人員的工作變得繁雜而無趣:
從上面的示意圖中可以看到,在應(yīng)用變大之后,軟件開發(fā)人員花在編譯及部署的時間明顯增多,甚至超過了他對代碼進(jìn)行更改并測試的時間,效率已經(jīng)變得十分低下。
在變得越來越大的同時,我們的應(yīng)用所使用的技術(shù)也會變得越來越多。這些技術(shù)有些是不兼容的,就比如在一個項(xiàng)目中大范圍地混合使用C++和Java幾乎是不可能的事情。在這種情況下,我們就需要拋棄對某些不兼容技術(shù)的使用,而選擇一種不是那么適合的技術(shù)來實(shí)現(xiàn)特定的功能。
除此之外,由于按照Monolith組織的代碼將只產(chǎn)生一個包含了所有功能的WAR包,因此在對服務(wù)的容量進(jìn)行擴(kuò)展的時候,我們只能選擇重復(fù)地部署這些WAR包來擴(kuò)展服務(wù)能力,而不是僅僅擴(kuò)展出現(xiàn)系統(tǒng)瓶頸的組成:
但是這種擴(kuò)展方式極大地浪費(fèi)了資源。就以上圖所展示的情況為例:在一個服務(wù)中,某個組成的負(fù)載已經(jīng)達(dá)到了90%,也就是到了不得不對服務(wù)能力進(jìn)行擴(kuò)容的時候了。而同一服務(wù)的其它三個組成的負(fù)載還沒有到其處理能力的20%。由于Monolith服務(wù)中的各個組成是打包在同一個WAR包中的,因此通過添加一個額外的服務(wù)實(shí)例雖然可以將需要擴(kuò)容的組成的負(fù)載降低到了45%,但是也使得其它各組成的利用率更為低下。
可以說,所有的不便都是由于Monolith服務(wù)中一個WAR包包含了該服務(wù)的所有功能所導(dǎo)致的。而解決該問題的方法就是Microservice架構(gòu)模式。
Microservice架構(gòu)模式
簡單地說,Microservice架構(gòu)模式就是將整個Web應(yīng)用組織為一系列小的Web服務(wù)。這些小的Web服務(wù)可以獨(dú)立地編譯及部署,并通過各自暴露的API接口相互通訊。它們彼此相互協(xié)作,作為一個整體為用戶提供功能,卻可以獨(dú)立地進(jìn)行擴(kuò)容。就以下圖所示的WikiPedia服務(wù)架構(gòu)為例:
從上圖中可以看到,WikiPedia包含了一系列服務(wù),如數(shù)據(jù)訪問服務(wù)Databases,搜索服務(wù)Search等。這些服務(wù)都包含了數(shù)量不等的服務(wù)實(shí)例,以確保能在不同負(fù)載的情況下為用戶提供優(yōu)質(zhì)的服務(wù)。在用戶的請求到達(dá)時,它們將協(xié)同工作,一起完成對用戶請求的響應(yīng)。
在使用Microservice架構(gòu)模式的情況下,軟件開發(fā)人員可以通過編譯并重新部署單個子服務(wù)的方式來驗(yàn)證自己的更改,而不再需要重新編譯整個應(yīng)用,從而節(jié)省了大量的時間。同時由于每個子服務(wù)是獨(dú)立的,因此各個服務(wù)內(nèi)部可以自行決定最為合適的實(shí)現(xiàn)技術(shù),使得這些子服務(wù)的開發(fā)變得更為容易。最后如果當(dāng)前系統(tǒng)的容量不夠了,那么我們只需要找到成為系統(tǒng)瓶頸的子服務(wù),并擴(kuò)展該子服務(wù)的容量即可:
Microservice經(jīng)驗(yàn)談
以上就是對Miscroservice架構(gòu)模式的介紹,是不是很簡單?實(shí)際上,這是一個正在發(fā)展的架構(gòu)模式。在眾多討論中,關(guān)于該模式的標(biāo)準(zhǔn)實(shí)現(xiàn),以及最佳實(shí)踐等眾多話題并沒有完全達(dá)成一致。因此我在這里介紹的,是各個論壇討論中基本達(dá)成一致意見的一系列經(jīng)驗(yàn)。而各位在實(shí)現(xiàn)自己的Microservice架構(gòu)模式時,一方面可以借鑒這些經(jīng)驗(yàn),另一方面也可以根據(jù)項(xiàng)目本身需求調(diào)整Microservice架構(gòu)模式的實(shí)現(xiàn)方法。
轉(zhuǎn)變你的視角
無論是在編寫一個服務(wù),還是在編寫一個桌面應(yīng)用,我們常常會首先嘗試將需要實(shí)現(xiàn)的功能分割為一系列組件,并圍繞著這些組件設(shè)計(jì)完成業(yè)務(wù)邏輯所需要的工作流及數(shù)據(jù)流。這種設(shè)計(jì)方法將導(dǎo)致實(shí)現(xiàn)業(yè)務(wù)邏輯的所有組件都運(yùn)行在同一個進(jìn)程之內(nèi),并且各個業(yè)務(wù)邏輯的實(shí)現(xiàn)也在同一個進(jìn)程之內(nèi)運(yùn)行:
但是在Microservice架構(gòu)模式中,我們需要更高一層的分割:在嘗試將需要實(shí)現(xiàn)的功能分割成為一系列組件之前,我們首先需要考慮如何將需要實(shí)現(xiàn)的功能交由彼此相互獨(dú)立的一系列服務(wù)來完成。例如在一個電子商務(wù)網(wǎng)站中,對用戶購買商品這一業(yè)務(wù)流程的支持就可以交由三個服務(wù)來完成:在用戶瀏覽商品的時候,其使用的是商品瀏覽服務(wù);在用戶將商品添加到購物車并生成訂單的時候,其使用的是訂單服務(wù);而在用戶進(jìn)行網(wǎng)上支付的時候,其使用的則是付款服務(wù)。根據(jù)這種分割思路,我們的應(yīng)用將運(yùn)行在三個獨(dú)立的進(jìn)程之中:
同時這三種服務(wù)各自的側(cè)重點(diǎn)并不相同:商品瀏覽服務(wù)中,對數(shù)據(jù)庫的讀操作比寫操作多得多,因此對讀操作進(jìn)行優(yōu)化將非常顯著地提高服務(wù)的運(yùn)行性能;而訂單服務(wù)則是寫操作居多,因此我們需要對訂單的寫入性能進(jìn)行優(yōu)化;付款服務(wù)涉及到用戶的財(cái)產(chǎn),因此其對安全要求會偏高一些。這種差異可能導(dǎo)致最適合實(shí)現(xiàn)這三個服務(wù)的技術(shù)各不相同。由于這些服務(wù)是完全獨(dú)立的,因此我們完全可以根據(jù)子服務(wù)的需求來決定所需要使用的技術(shù),而不再需要考慮這些類庫是否與已有系統(tǒng)兼容。
使用最合適的技術(shù)所帶來的優(yōu)點(diǎn)就是,服務(wù)的代碼會變得非常清晰明了,甚至在有些情況下可以達(dá)到簡潔優(yōu)雅的程度。在一些討論中,有些人甚至建議一個服務(wù)只需要10到100行代碼(他們常用簡寫LoC,即Lines of Code)。再加上服務(wù)已經(jīng)獨(dú)立出來,而不再與其它服務(wù)混合在一起,因此正確地使用Microservice架構(gòu)模式大大提高了代碼的維護(hù)性以及新人上手的速度,也有助于技術(shù)人員在日常工作中進(jìn)行技術(shù)集的更新及轉(zhuǎn)換。
但是這種對于服務(wù)的分割和組件之間的分割并不相同。最重要的一點(diǎn)就是在各個服務(wù)之間進(jìn)行通訊的消耗相對于在同一個進(jìn)程中而言是非常大的。在設(shè)計(jì)一個組件的時候,我們需要考慮該組件所給出的接口能夠盡可能地滿足當(dāng)前及今后的一系列可以預(yù)見的需求,這便要求該組件所提供的API具有一定的前向兼容性,并擁有一系列其它特性,如靈活性,擴(kuò)展性等等。這常常導(dǎo)致該組件所提供的API具有較細(xì)的粒度。在程序運(yùn)行時,對該組件所提供的API的調(diào)用就在當(dāng)前進(jìn)程中進(jìn)行,速度非常快,因此頻繁地對該細(xì)粒度API進(jìn)行調(diào)用并沒有太大的問題。但是一個跨服務(wù)調(diào)用所需要的時間則比進(jìn)程內(nèi)調(diào)用的時間長很多。如果在處理一個請求的時候需要太多的跨服務(wù)調(diào)用,那么整個應(yīng)用的性能將變得無法忍受。因此我們在執(zhí)行服務(wù)分割時定義的API需要是粗粒度的API。
就讓我們以一個電子商務(wù)網(wǎng)站為例。在為用戶生成訂單時,電子商務(wù)網(wǎng)站常常需要列出各個商品的主要信息,商品的價(jià)格,優(yōu)惠幅度,并通過庫存系統(tǒng)檢驗(yàn)該商品的庫存,從而得到整個訂單的內(nèi)容。如果每次與其它服務(wù)溝通都需要100毫秒,而且整個訂單包含了20件貨物,那么系統(tǒng)準(zhǔn)備訂單的時間就會達(dá)到8秒(100ms * 4次調(diào)用 * 20件商品)。這從用戶的角度來說是不可以接受的性能。而且隨著訂單中所包含商品數(shù)量的增多,系統(tǒng)準(zhǔn)備訂單的時間會線性增長,進(jìn)而使得系統(tǒng)的性能更加不可忍受。
究其原因,實(shí)際上還是因?yàn)闇?zhǔn)備訂單所調(diào)用的API的粒度太細(xì)了。如果訂單系統(tǒng)能夠一次性地把一件商品的主要信息,價(jià)格,優(yōu)惠幅度以及庫存信息從商品服務(wù)中取回來,那么其效率就將提高四倍。如果訂單系統(tǒng)不需要為每件商品依次發(fā)送請求,而是可以通過一次性地服務(wù)間調(diào)用就能取回所有需要的信息,那么系統(tǒng)準(zhǔn)備訂單的時間將不會再隨著訂單的增大而增長。因此在Microservice架構(gòu)模式中,各個服務(wù)應(yīng)該提供可以被靈活使用的粗粒度API,以減少各種跨服務(wù)調(diào)用的消耗。
除了各個服務(wù)所提供的API的粒度,服務(wù)分割的粒度也是在服務(wù)分割過程中需要考慮的因素。如果一個服務(wù)的粒度太小,那么它所提供的API的粒度也不會高。一個較為普遍的看法是,在Microservice架構(gòu)模式中,一個服務(wù)需要能夠獨(dú)立地完成特定的業(yè)務(wù)邏輯,至少是某個獨(dú)立資源的CRUD操作。例如在電子商務(wù)網(wǎng)站中,我們需要一個服務(wù)能夠獨(dú)立地完成對商品相關(guān)信息的讀取,如商品的主要信息,商品的價(jià)格,參與的優(yōu)惠活動等。
這里有一個例外,那就是公共功能的處理。試想在一個應(yīng)用中,我們常常需要一個權(quán)限管理組件來管理用戶所具有的各個權(quán)限。權(quán)限管理組件常常實(shí)現(xiàn)了一種公用的安全模型(Security Model),如ACL(Access control list),RBAC(Role-based access control)等。在每次訪問一個子服務(wù)的時候,這些服務(wù)都需要檢查用戶所具有的權(quán)限:
發(fā)現(xiàn)問題了么?是的,每次對一個產(chǎn)品系統(tǒng)及訂單系統(tǒng)的調(diào)用都需要從權(quán)限系統(tǒng)中得到當(dāng)前用戶的權(quán)限,才能決定用戶是否能夠訪問特定信息。如果這樣的公共服務(wù)很多,那么該系統(tǒng)的性能將會變得非常差。
解決該問題的一種方法就是在各個系統(tǒng)中將下次還能夠使用的信息緩存起來,也就是在這些系統(tǒng)中為用戶創(chuàng)建一個會話。由于每個系統(tǒng)可能由多個服務(wù)實(shí)例所組成,為了能夠重復(fù)利用會話中所儲存的信息,減少向公共服務(wù)發(fā)送請求的次數(shù),我們需要通過負(fù)載平衡技術(shù)讓系統(tǒng)中的同一個服務(wù)實(shí)例處理同一個用戶的請求。有關(guān)如何實(shí)現(xiàn)該功能,請見我的另一篇文章《企業(yè)級負(fù)載平衡簡介》。
除了性能問題之外,公共服務(wù)還會與各個服務(wù)產(chǎn)生一種邏輯上的依賴關(guān)系。讓我們繼續(xù)通過權(quán)限系統(tǒng)這個例子進(jìn)行討論。當(dāng)權(quán)限管理的組成存在于各個服務(wù)中的時候,我們可以直接通過傳入用戶的信息以及需要訪問的資源就能判斷出到底用戶是否能夠訪問特定資源。也就是說,從權(quán)限管理組成所返回的實(shí)際上就是一個布爾類型的數(shù)據(jù)。但如果權(quán)限管理不再是一個組成,而是一個服務(wù),那么為了避免每次都調(diào)用權(quán)限管理服務(wù),我們需要在用戶的會話中記錄用戶所具有的權(quán)限。在用戶下次訪問該服務(wù)的時候,我們可以通過直接檢查該用戶所具有的所有權(quán)限就能決定其是否能夠訪問特定資源了。這些在用戶會話中記錄的權(quán)限常常具有其特定的表現(xiàn)方式,例如特定形式的字符串,而這種字符串表示需要同時被服務(wù)和權(quán)限管理服務(wù)所理解,從而造成了這兩個服務(wù)之間的耦合:
但是這種方式為子服務(wù)增強(qiáng)了對權(quán)限系統(tǒng)的依賴性。和組件之間的耦合一樣,增大的耦合性會導(dǎo)致服務(wù)的重用性下降。
所以說,如何對服務(wù)進(jìn)行分割實(shí)際上是Microservice架構(gòu)模式中最需要技巧的事情。在分割過程中,服務(wù)的總體性能是至關(guān)重要的,而各個服務(wù)的獨(dú)立性也是大家所最為關(guān)心的特性。當(dāng)然,Microservice架構(gòu)模式仍在逐漸發(fā)展中,因此相信會有越來越多的實(shí)踐經(jīng)驗(yàn)被大家所發(fā)掘出來,進(jìn)而指導(dǎo)我們更好地對服務(wù)進(jìn)行分割。
共享服務(wù)
在前面一節(jié)中,我們已經(jīng)提到了公共服務(wù)。實(shí)際上,這是Microservice架構(gòu)模式中最需要技巧的一部分。
實(shí)際上,Microservice架構(gòu)模式實(shí)現(xiàn)中常常需要一系列公有服務(wù)以輔助整個應(yīng)用的運(yùn)行。除了我們剛剛提到的權(quán)限管理服務(wù),我們還需要能夠監(jiān)控各個服務(wù)實(shí)例的服務(wù)狀態(tài),服務(wù)實(shí)例的添加刪除升級管理等等。這些服務(wù)在各個子服務(wù)的服務(wù)實(shí)例之間共享,甚至可以在其它應(yīng)用中被重用。
只是很多人擁有一個這樣的誤區(qū),那就是Microservice架構(gòu)模式可以讓服務(wù)的開發(fā)變得更容易。而實(shí)際情況則恰好相反。在剛開始使用Microservice架構(gòu)模式開發(fā)應(yīng)用的時候,其效率是明顯低于通過Monolith進(jìn)行開發(fā)的:
從上圖中可以看到,在剛開始的階段,使用Microservice架構(gòu)模式開發(fā)應(yīng)用的效率明顯低于Monolith。但是隨著應(yīng)用規(guī)模的增大,基于Microservice架構(gòu)模式的開發(fā)效率將明顯上升,而基于Monolith模式開發(fā)的效率將逐步下降。
為什么呢?這是因?yàn)镸icroservice是一個架構(gòu)模式,而不是一個特定的技術(shù)解決方案。其并不會將開發(fā)中的各個難點(diǎn)全部轉(zhuǎn)移,而只是允許通過更為合適的技術(shù)來適當(dāng)簡化單個子服務(wù)的開發(fā),或者繞過開發(fā)中可能遇到的部分難點(diǎn)。但是為了支持各個子服務(wù)的運(yùn)行,我們還需要創(chuàng)建一系列公共服務(wù)。這些公共服務(wù)需要在編寫第一個子服務(wù)的同時進(jìn)行。這是導(dǎo)致Microservice架構(gòu)模式在開發(fā)初期會具有較低效率的一個原因。
然而使用特定技術(shù)并不會繞過開發(fā)中所能遇到的所有難點(diǎn)。由于在Microservice架構(gòu)中,各個子服務(wù)都集中精力處理本身的業(yè)務(wù)邏輯,而所有的公共功能都交由公共服務(wù)來完成,因此公共服務(wù)在保持和各個子服務(wù)的松耦合性的同時還需要提供一個足夠通用的,能夠在一定程度上滿足所有當(dāng)前和未來子服務(wù)要求的解決方案。而這也是導(dǎo)致Microservice架構(gòu)模式在開發(fā)初期會具有較低效率的另外一個原因。
而在開發(fā)的后期,隨著Monolith模式中應(yīng)用的功能逐漸變大,增加一個新的功能會影響到該應(yīng)用中的很多地方,因此其開發(fā)效率會越來越差。反過來,由于Microservice架構(gòu)模式中的各個子服務(wù)所依賴的公共服務(wù)已經(jīng)完成,而且子服務(wù)本身可以選擇適合自己的實(shí)現(xiàn)技術(shù),因此子服務(wù)的實(shí)現(xiàn)通常只需要關(guān)注自身的業(yè)務(wù)邏輯即可。這也是Microservice架構(gòu)模式在后期具有較高效率的原因。
當(dāng)我們再次通過Microservice架構(gòu)模式搭建應(yīng)用的時候,其在開發(fā)時的效率劣勢也將消失,原因就是因?yàn)樵谇耙淮位贛icroservice架構(gòu)模式開發(fā)的時候,我們已經(jīng)創(chuàng)建過一次公共服務(wù),因此在這個新的應(yīng)用中,我們將這些公共服務(wù)拿來并稍事改動即可:
從上圖中可以看到,雖然我們?nèi)匀恍枰ㄒ恍r間來對公共服務(wù)進(jìn)行一些修改,但是此時所導(dǎo)致的效率下降已經(jīng)不再那么明顯了。也就是說,就算是在前期,我們已經(jīng)擁有了較高的開發(fā)效率。
而且隨著Microservice架構(gòu)模式的不斷流行,在網(wǎng)絡(luò)上會有越來越多的用戶共享自己的公共服務(wù)解決方案。那么第一次按照Microservice架構(gòu)模式編寫應(yīng)用所導(dǎo)致的性能下降也會逐漸變得越來越小。
模型匹配
OK。在介紹了共享服務(wù)之后,我們就可以討論Microservice架構(gòu)模式中的另外一個問題:模型匹配了。在Microservice中,各個服務(wù)是彼此獨(dú)立的,而且是關(guān)注于自身業(yè)務(wù)邏輯的。因此在看待一個事物的時候,Microservice可能擁有不同的視角,進(jìn)而造成了各個子服務(wù)中的對應(yīng)模型并不匹配。
例如在一個IaaS云中,一個用戶所具有的角色可能會根據(jù)他所擁有的職責(zé)來劃分:云上擁有一系列用于監(jiān)控的用戶,用來完成對云的整體運(yùn)行監(jiān)控等工作(不包含查看用戶數(shù)據(jù),這是個安全問題)。同時云上的用戶又可以分為帳號管理員,Tenant管理員,資源管理員以及普通用戶等。而在其上運(yùn)行的應(yīng)用即服務(wù)(AaaS,Application as a Service)中,其用戶的職責(zé)劃分可能是另一個樣子:在AaaS上定義應(yīng)用的是應(yīng)用架構(gòu)師,負(fù)責(zé)應(yīng)用部署及維護(hù)的則是運(yùn)維人員。在應(yīng)用架構(gòu)師設(shè)計(jì)一個應(yīng)用的時候,其并需要擁有IaaS云上訪問資源的權(quán)限,卻并不需要分配資源的權(quán)限,但是運(yùn)維人員需要擁有該權(quán)限以對應(yīng)用進(jìn)行部署和維護(hù)。
也就是說,IaaS云中的權(quán)限劃定和AaaS服務(wù)中的權(quán)限劃定并不一樣。通常情況下,我們常常在業(yè)務(wù)集成時執(zhí)行一次對權(quán)限的匹配:
從上圖中可以看出,由于AaaS服務(wù)是運(yùn)行在IaaS之上的,因此為了能夠操作IaaS中所包含的各個資源,AaaS服務(wù)需要將自己的用戶角色匹配到IaaS所定義的角色上。例如應(yīng)用架構(gòu)師需要能夠在定義應(yīng)用的時候需要知道IaaS上所具有的資源,并需要能夠指定到底哪些人可以使用這些應(yīng)用,因此其需要擁有IaaS的Tenant管理員,資源管理員及普通用戶三種角色。而AaaS上的運(yùn)維人員則只需要在部署和維護(hù)時察看IaaS上所擁有的資源,因此其只需要資源管理員及普通用戶兩種角色。
但是這么做有兩點(diǎn)不好的地方:如果Microservice中只包含了幾個服務(wù),而且這種服務(wù)之間的依賴關(guān)系并不是很多,那么這種服務(wù)匹配還能夠解決,但是如果整個系統(tǒng)之間各個子服務(wù)的溝通很多,那么在各個子服務(wù)之間進(jìn)行角色匹配將變成一個噩夢:
解決該問題的方法就是使用我們上節(jié)所介紹的公共服務(wù)對它們進(jìn)行管理。在提供一個集中的公共服務(wù)的情況下,我們就不再需要處理這么多的模型轉(zhuǎn)化了:
除此之外,僅僅簡單地對角色進(jìn)行匹配實(shí)際上并不那么合適:就應(yīng)用架構(gòu)師而言,其需要的是查看當(dāng)前的已有資源,卻不需要對資源進(jìn)行分配。因此其需要的是對資源的讀權(quán)限。而運(yùn)維人員則不僅僅需要能夠讀取資源信息,更需要對資源進(jìn)行分配,因此其需要的是資源的讀寫權(quán)限。如果僅僅像上面那樣在IaaS層為應(yīng)用架構(gòu)師賦予對資源的讀寫權(quán)限,那么應(yīng)用架構(gòu)師就可能擁有了錯誤的權(quán)限,進(jìn)而執(zhí)行了錯誤的操作。而相對地較為合適的方式則是對這些權(quán)限進(jìn)行細(xì)分,即在權(quán)限中區(qū)分讀寫權(quán)限等:
因此在集中的公共服務(wù)中,我們需要使用較為細(xì)粒度的模型。該細(xì)粒度模型需要具有較高的靈活性,以能夠無損地表示各個服務(wù)中的相應(yīng)模型。
相信您現(xiàn)在已經(jīng)能夠看出,雖然說Microservice架構(gòu)模式將單個子服務(wù)的實(shí)現(xiàn)簡化了,但是復(fù)雜化了數(shù)據(jù)的處理。因此相較于我們以往所編寫的應(yīng)用,Microservice架構(gòu)模式會在數(shù)據(jù)相關(guān)的一些特性上遇到一系列麻煩。
一個較為常見的麻煩就是保持多個子服務(wù)之間數(shù)據(jù)的一致性。我們知道,在服務(wù)中,保持一致性的工作常常是由事務(wù)來完成的。而如果希望在Microservice架構(gòu)模式實(shí)現(xiàn)中保持子服務(wù)之間數(shù)據(jù)的一致性,我們可能就需要使用分布式事務(wù)了。但是分布式事務(wù)本身就是一個非常復(fù)雜并且難以操作的東西,因此就現(xiàn)在而言,這種問題實(shí)際上是非常難以解決的。但是反過來講,事務(wù)本身也是表示一種邏輯上的強(qiáng)耦合,因此我們需要真正反思的則是這些需要使用事務(wù)來保持?jǐn)?shù)據(jù)一致性的子服務(wù)是否應(yīng)該屬于同一個服務(wù)。當(dāng)然,我們可以在某種程度上借鑒NoSQL數(shù)據(jù)庫中的一些做法。例如在一個服務(wù)更新了數(shù)據(jù)以后,我們使用一種異步機(jī)制來保持?jǐn)?shù)據(jù)的一致性,就好像很多NoSQL數(shù)據(jù)庫不保證用戶的數(shù)據(jù)立即可讀一樣。
另一個較為常見的麻煩就是粒度的問題。我們在前面已經(jīng)說過,在Microservice的各個子服務(wù)之間進(jìn)行服務(wù)間調(diào)用效率是十分低下的。為了減少多次服務(wù)間調(diào)用,各個子服務(wù)所提供的API的粒度需要盡量地粗,卻需要盡量地保持靈活性。最好的情況就是可以通過一次服務(wù)間調(diào)用來得到所有想要的信息。
項(xiàng)目管理
除了上面所討論的一系列技術(shù)因素之外,Microservice架構(gòu)模式的開發(fā)還存在著一系列項(xiàng)目管理上的難題。
首先,由于Microservice架構(gòu)模式中的各個子服務(wù)可能使用了不同的技術(shù)搭建,例如有些子服務(wù)是由Java開發(fā)的,有些則是由Python開發(fā)的,而且它們所使用的Servlet容器并不相同,因此由Microservice架構(gòu)模式所搭建的應(yīng)用可能需要非常復(fù)雜的環(huán)境設(shè)置。這對于傳統(tǒng)的運(yùn)維人員來說是非常困難的一個任務(wù)。而相對于這些運(yùn)維人員而言,負(fù)責(zé)各個子服務(wù)開發(fā)的開發(fā)人員才是有關(guān)該服務(wù)運(yùn)行及部署的專家。因此在Microservice架構(gòu)模式中,開發(fā)及運(yùn)維的職責(zé)均發(fā)生了變化:開發(fā)人員不僅僅需要負(fù)責(zé)子服務(wù)代碼的編寫,還需要考慮該子服務(wù)的日常運(yùn)維。而運(yùn)維人員需要向開發(fā)人員給出一些運(yùn)維相關(guān)的建議,并在總的方向上掌控產(chǎn)品的日常運(yùn)維。
這樣做的好處則在于:開發(fā)人員會直接接觸到生產(chǎn)環(huán)境,可以快速地跟蹤并解決問題,而不再需要通過客戶及運(yùn)維人員的轉(zhuǎn)述等步驟才開始處理問題,也避免了在轉(zhuǎn)述過程中出現(xiàn)的偏差。除此之外,開發(fā)人員也能更清楚地了解用戶到底是如何使用他們所創(chuàng)建出來的產(chǎn)品的,進(jìn)而創(chuàng)建出來更容易被使用及管理的子服務(wù)。
但是這也會導(dǎo)致項(xiàng)目管理出現(xiàn)一些困難。首先,不論是開發(fā)人員還是管理者都需要了解并處理一系列運(yùn)維相關(guān)的問題。這會分散他們的注意力,使得開發(fā)效率的降低。其次,由于一個子服務(wù)常常同時包含前端,后臺,數(shù)據(jù)庫,測試,甚至運(yùn)維相關(guān)的一些任務(wù),因此子服務(wù)的開發(fā)人員常常需要了解服務(wù)開發(fā)的大部分組成。這種人才在中國市場上并不多見,因此比較搶手。而且由于一個開發(fā)人員需要接觸太多的功能和技術(shù),因此很多時候沒有辦法深入地研究它們。由此所導(dǎo)致的問題則是,在遇到較為困難的問題時,軟件開發(fā)人員需要花費(fèi)較多的時間來分析并解決該問題。如果該問題較為嚴(yán)重,那么它將會嚴(yán)重影響整個組的開發(fā)進(jìn)度。從項(xiàng)目管理的角度來講,這實(shí)際上是一件非常危險(xiǎn)的事情。
一個理想的解決方案就是,當(dāng)前子服務(wù)所使用的各個技術(shù)都有一個專家。但是一個全棧開發(fā)人員,還需要是某一方面的技術(shù)專家,雇傭該人的成本可想而知。
除此之外,我們還需要在按照Microservice架構(gòu)模式開發(fā)的時候使用一系列標(biāo)準(zhǔn)化的開發(fā)及測試流程。其中和Microservice最自然契合的就是現(xiàn)在最為流行的Continuous Delivery,或被稱為是DevOps。在這些自動化流程的幫助下,軟件開發(fā)人員可以快速地完成一次迭代:在對代碼更改完畢以后,軟件開發(fā)人員可以直接開始對自己的更改進(jìn)行編譯,運(yùn)行單元測試及功能測試。接下來,系統(tǒng)將會把剛剛編譯好的代碼自動進(jìn)行部署,并在整個系統(tǒng)中執(zhí)行集成測試。在集成測試完畢之后,質(zhì)量管理人員或軟件開發(fā)人員自己會在該系統(tǒng)中進(jìn)行一次測試,并在完成測試后進(jìn)行復(fù)雜的性能測試,并在通過性能測試后進(jìn)行部署。
所有這一切實(shí)際上都和使用Monolith開發(fā)時所使用的流程類似。唯一不同的是,在基于Microservice架構(gòu)模式的開發(fā)中,這種自動化的流程變得更為重要了。因?yàn)榛贛icroservice架構(gòu)模式所搭建的應(yīng)用常常使用了不同的邏輯,因此部署一個完整的環(huán)境就會變得非常復(fù)雜。所以由這些自動化流程來負(fù)責(zé)測試環(huán)境的部署則大大地減輕了軟件開發(fā)人員的負(fù)擔(dān),也是提高軟件開發(fā)人員工作效率的基礎(chǔ)。
同時由于軟件開發(fā)人員需要隨時執(zhí)行應(yīng)用程序的部署來測試自己剛剛所做的更改,因此其需要能夠隨時分配到其所需要的各個資源,如部署應(yīng)用所需要的計(jì)算資源,內(nèi)存以及存儲等。而這種功能則正是云這種商業(yè)模式所提供的功能。因此在開發(fā)基于Microservice架構(gòu)模式的應(yīng)用時,我們則盡量基于某些云來開展我們的持續(xù)開發(fā)流程。
Microservice實(shí)現(xiàn)
在本節(jié)中,我們將對實(shí)現(xiàn)Microservice架構(gòu)模式時所常用的一些方法進(jìn)行講解。
相信大家的第一個問題就是,Microservice架構(gòu)模式中各個子服務(wù)應(yīng)該如何相互協(xié)作以向用戶提供服務(wù)的呢?按照上面我們的講解,Microservice架構(gòu)模式中各個子服務(wù)應(yīng)該是獨(dú)立的,否則它們之間將產(chǎn)生耦合,進(jìn)而帶來一系列問題:這些子服務(wù)彼此不獨(dú)立,需要使用分布式事務(wù)保持其數(shù)據(jù)一致性,子服務(wù)不易被重用等。但是如果這些子服務(wù)絕對獨(dú)立,甚至不包含一點(diǎn)點(diǎn)邏輯上的耦合,那么它們之間也將無法進(jìn)行協(xié)作。因此在論壇討論中常常出現(xiàn)的問題就是,這些子服務(wù)之間哪里可以出現(xiàn)耦合?可以出現(xiàn)什么程度的耦合?
這個問題實(shí)際上非常簡單,那就是UI。我們知道,在一個BS服務(wù)中,服務(wù)端和客戶端之間存在著一定程度的耦合。兩者通過服務(wù)所暴露的API進(jìn)行溝通。而基于Microservice架構(gòu)模式的服務(wù)也不例外:
既然運(yùn)行在用戶瀏覽器中的UI需要與其它各個子服務(wù)進(jìn)行交互,那么它完全可以作為一個中介者來完成各個子服務(wù)之間的交互。例如在顯示產(chǎn)品頁面的時候,該頁面邏輯會向產(chǎn)品服務(wù)及庫存服務(wù)同時發(fā)送請求,以并行地得到產(chǎn)品的詳細(xì)信息以及該產(chǎn)品的當(dāng)前庫存。
因此在一個基于Microservice架構(gòu)模式的服務(wù)中,常常會出現(xiàn)一個前端服務(wù)。該服務(wù)所提供的頁面會與各個服務(wù)溝通。但是它實(shí)際上與各個子服務(wù)之間卻不需要通訊:
或許您會說,在這種情況下,我們的各個子服務(wù)就沒有UI了。而UI服務(wù)不僅僅需要處理所有的前端業(yè)務(wù)邏輯,而且隨著時間的推移,其可能會變成另外一個龐然大物。除此之外,如果希望整個平臺能夠允許第三方服務(wù)接入,那么這種打包在一起的UI服務(wù)將變成整個平臺擴(kuò)展性的阻礙。
是的。如果需要解決這個問題,那么您就需要在應(yīng)用中嘗試借鑒Service Locator模式。此時我們需要的則是一個UI框架,其允許用戶通過特定方式在應(yīng)用中插入各個子服務(wù)所提供的UI,并允許您通過一些機(jī)制來發(fā)現(xiàn)已經(jīng)在平臺中注冊的具有特定功能的API,并允許您對該API進(jìn)行調(diào)用。我相信,隨著Microservice架構(gòu)模式的不斷發(fā)展,會有越來越多的支持這種擴(kuò)展方式的UI類庫出現(xiàn)。
另外一種模式則是Message Broker。簡單地說,Message Broker就是一個消息的中轉(zhuǎn)平臺。該平臺允許其它組成向其中注冊消息,也允許其它組成偵聽消息。當(dāng)一個組成將一個消息發(fā)送到了Message Broker之上后,其它偵聽該消息的各個組成則會根據(jù)消息中所包含的信息更新自己的狀態(tài)。
反過來,如果您的服務(wù)需要支持移動設(shè)備,如手機(jī),iPad等,我們就不能讓這些移動設(shè)備一個一個地訪問子服務(wù)了。這是因?yàn)檫@些移動設(shè)備的帶寬一般來說都非常小,而且用戶常常處于信號不是很好的地方,因此在向這些子服務(wù)一個個地發(fā)送請求將快速消耗掉它們所擁有的有限的帶寬。為了解決這個問題,我們常常需要在這些子服務(wù)前搭建一個代理服務(wù)。該代理服務(wù)會將用戶請求根據(jù)業(yè)務(wù)邏輯拆分為對各個子服務(wù)的請求,并將各個子服務(wù)所返回的結(jié)果歸納為一個響應(yīng)返回給用戶:
當(dāng)然,上面所介紹的僅僅是當(dāng)前論壇討論中所常常提到的一種搭建基于Microservice架構(gòu)模式應(yīng)用的方式。或許在不久的將來,您會看到設(shè)計(jì)得越來越精巧的各種模式出現(xiàn)。
在講解完這些子服務(wù)該如何展現(xiàn)給用戶之后,我們就來講解一下如何創(chuàng)建各個子服務(wù)所需要的公共服務(wù)。之前我們已經(jīng)提到過,由于對公共服務(wù)的調(diào)用是一個跨進(jìn)程調(diào)用,因此其相較于進(jìn)程內(nèi)調(diào)用效率非常低下。在這種情況下,我們需要盡量避免對該公共服務(wù)的重復(fù)調(diào)用。為了達(dá)到該目標(biāo),我們需要盡量使用戶訪問同一個子服務(wù)實(shí)例,并且在該用戶的會話中緩存從公共服務(wù)中所得到的信息。
因此在同一個子服務(wù)的各個服務(wù)實(shí)例上,我們需要盡量使用負(fù)載平衡服務(wù)的Sticky Session的功能,并在一次公共服務(wù)調(diào)用中取得多項(xiàng)信息。例如在查看用戶的權(quán)限時,我們不是返回用戶是否具有特定權(quán)限,而是該用戶擁有哪些權(quán)限。當(dāng)然,這不僅僅需要從Microservice這種架構(gòu)模式的方面來考慮,還需要同時兼顧安全,維護(hù)性等一系列問題。
簡單地說,在兼顧其它方面的情況下,我們需要將公共服務(wù)API的粒度定得粗一些,同時也需要具有一定的靈活性,從而通過減少服務(wù)間調(diào)用來避免整個服務(wù)的性能瓶頸。
既然說到了API的粒度,那我們就需要討論一下各個子服務(wù)所提供的API了。和公共服務(wù)一樣,各個子服務(wù)所暴露的API也應(yīng)該具有較粗的粒度以及較大的靈活性。除此之外,我們還需要讓這些子服務(wù)所暴露的API具有盡量一致的樣式,如定義一系列RESTful的API。在這種情況下,與這些服務(wù)進(jìn)行交互的組成,如網(wǎng)頁的UI,才能具有可以接受的維護(hù)性。
一個經(jīng)驗(yàn)性的觀點(diǎn)則是,Microservice架構(gòu)模式中的“開”是各個服務(wù)的內(nèi)部實(shí)現(xiàn),而其中的“閉”則是各個服務(wù)之間相互溝通的方式。
如果您需要從頭開始搭建一個服務(wù),那么您需要首先考慮如何對這些服務(wù)進(jìn)行劃分,并在創(chuàng)建第一個服務(wù)的時候開始搭建出各個公共服務(wù)的雛形,同時確定各個服務(wù)之間溝通所需要遵守的協(xié)議。當(dāng)越來越多的子服務(wù)被創(chuàng)建出來之后,您需要逐漸豐富各個公共服務(wù)所提供的功能,使其逐漸變?yōu)楣δ軓?qiáng)大的,可重用的服務(wù)。
如果您已經(jīng)擁有一個Monolith服務(wù),并且希望通過采用Microservice架構(gòu)模式來緩解當(dāng)前Monolith模式服務(wù)所具有的一系列問題,那么您首先需要創(chuàng)建一個獨(dú)立的服務(wù),并通過一個粘合層來與該Monolith服務(wù)交互。在該過程中,您可能需要將Monolith服務(wù)的內(nèi)部接口逐漸暴露出來,以供這個新的服務(wù)使用。而這就是在抽象公共服務(wù)的過程。
接下來,您就需要根據(jù)上一步中所得到的接口來逐步將Monolith服務(wù)中的公共服務(wù)剝離。在剝離過程中,您腦中需要記得的一句話還是:粗粒度,靈活的API。而其內(nèi)部實(shí)現(xiàn)到底是什么樣的,實(shí)際上并不會影響到您的剝離結(jié)果。
最后就是再從Monolith中剝離其它服務(wù)了。此時我們最需要考慮的就是在服務(wù)中具有鮮明特點(diǎn)的各個服務(wù),如對資源的要求與整個Monolith服務(wù)格格不入,或者使用了和Monolith很難兼容的技術(shù)等。
最后一種情況就是多個服務(wù)集成的情況。在產(chǎn)品的逐漸迭代過程中,我們常常會遇到需要將多個產(chǎn)品集成成為一個產(chǎn)品以提高整體競爭力的情況。這常常發(fā)生在盈利產(chǎn)品和其它非盈利產(chǎn)品之間。而這正是實(shí)踐Microservice架構(gòu)模式的絕佳機(jī)會。此時我們僅僅需要暴露一系列Monolith服務(wù)中的接口并創(chuàng)建粘合層即可。
Microservice的優(yōu)點(diǎn)與劣勢
好,在前面我們已經(jīng)講解了很多有關(guān)Microservice架構(gòu)模式的經(jīng)驗(yàn)性方法和相關(guān)知識。那我們現(xiàn)在回顧一下Microservice所具有的一系列優(yōu)點(diǎn)和劣勢,以使您能夠在采用Microservice架構(gòu)模式之前全面地衡量該方案所可能得到的好處及遇到的困難。
首先,由于Microservice架構(gòu)模式中的每個子服務(wù)都可以獨(dú)立于其它服務(wù)執(zhí)行,因此其常常具有更好的服務(wù)邊界。而這個明確的服務(wù)邊界則會帶來一系列好處:在Microservice架構(gòu)模式中,各個子服務(wù)執(zhí)行所需要的業(yè)務(wù)邏輯都相對集中于子服務(wù)內(nèi)。因此其實(shí)現(xiàn)代碼相對容易理解,并且便于維護(hù)。另外各個子服務(wù)所具有的結(jié)構(gòu),運(yùn)行流程及數(shù)據(jù)模型都能夠更貼近于子服務(wù)所表示的業(yè)務(wù)邏輯,因此在代碼的開發(fā)速度和維護(hù)性上得到了大大地增強(qiáng)。同時各個子服務(wù)可以選擇最適合實(shí)現(xiàn)業(yè)務(wù)邏輯的技術(shù),進(jìn)而使得各個服務(wù)的開發(fā)變得更為容易。同時在出現(xiàn)新的更適合的技術(shù)時,我們可以較為容易地在各個子服務(wù)內(nèi)部對原有的實(shí)現(xiàn)技術(shù)進(jìn)行替換。
獨(dú)立性也意味著擴(kuò)展性的增強(qiáng)。在Microservice架構(gòu)模式中,各個子服務(wù)可以根據(jù)自身的負(fù)載獨(dú)立地進(jìn)行擴(kuò)容,如Scale Up或Scale Out等。不僅如此,我們還可以根據(jù)子服務(wù)自身的特性為其準(zhǔn)備特定的硬件設(shè)備,使得其運(yùn)行在更適合的服務(wù)器上。同時這種獨(dú)立性還可以使得各個子服務(wù)可以被重用。
同時這種獨(dú)立性也可以增加整個服務(wù)的容錯能力。例如如果一個子服務(wù)由于種種原因無法繼續(xù)提供服務(wù),其它子服務(wù)仍然可以獨(dú)立地處理用戶的請求。
另外,各個子服務(wù)的獨(dú)立部署能力也可以大大地提高Continuous Delivery的運(yùn)行效率。畢竟在這種情況下,軟件開發(fā)人員只需要重新部署更改過的子服務(wù)就可以了。
由于Microservice架構(gòu)模式中的各個子服務(wù)無論是在代碼量方面還是最終生成的WAR包方面都較Monolith架構(gòu)所搭建的服務(wù)小,因此在IDE支持,啟動速度方面都具有相當(dāng)?shù)膬?yōu)勢。同時,這種小粒度的服務(wù)已經(jīng)可以由一個幾個人所組成的小組來完成,而不再需要通過來自世界各地的不同小組協(xié)同開發(fā),進(jìn)而大大降低了溝通成本,提高了開發(fā)的效率。
但是反過來,Microservice架構(gòu)模式中各個子服務(wù)的獨(dú)立性也會導(dǎo)致一系列問題。最明顯的就是需要多個子服務(wù)相互配合的情況。由于這些子服務(wù)是不同的進(jìn)程,因此在這些進(jìn)程之間保持?jǐn)?shù)據(jù)的一致性,或添加一個新的跨子服務(wù)的用戶用例實(shí)際上都是一件非常麻煩的事情。而且對這些獨(dú)立服務(wù)在整個系統(tǒng)中是否能夠工作的測試需要運(yùn)行大量的集成測試。而如果需要快速地對這些子服務(wù)進(jìn)行開發(fā)和迭代,那么我們就需要每個開發(fā)人員都能夠?qū)I(yè)并高效地使用一系列自動化工具。這實(shí)際上也是一個不低的要求。
除此之外,基于性能考慮,各個子服務(wù)所提供的接口將是粗粒度的,卻具有較高靈活性的API。但是這種API擁有一個較明顯的缺陷,那就是越靈活的API,其使用起來的難度就越大。因此對于服務(wù)的用戶而言,其上手的難度則相對增加了。
另外,如何規(guī)范化各個子服務(wù)之間的溝通協(xié)議也是一個非常具有挑戰(zhàn)性的事情。因?yàn)樵贛icroservice架構(gòu)模式中,我們常常需要創(chuàng)建一系列公共服務(wù)。這些公共服務(wù)常常暴露特定樣式的接口以供其它服務(wù)調(diào)用。因此我們需要在這些接口上保持一致性,進(jìn)而才能夠更自然地編寫各個子服務(wù)的內(nèi)部邏輯并暴露適當(dāng)?shù)慕涌凇5欠催^來,一致的接口樣式常常會導(dǎo)致各個服務(wù)的自然實(shí)現(xiàn)需要向這些標(biāo)準(zhǔn)進(jìn)行妥協(xié)。因此我們常常需要在兩者之間平衡。
這些平衡方法包括標(biāo)準(zhǔn)化各個服務(wù)所暴露的接口,使用固定的幾種方式對子服務(wù)進(jìn)行集成,保持?jǐn)?shù)據(jù)模型格式的一致性等。這些實(shí)際上都是我們自由編寫各個子服務(wù)的障礙。對此采取多么嚴(yán)格的規(guī)范實(shí)際上是需要通過經(jīng)驗(yàn)累積來完成的,因此這大大提高了使用Microservice架構(gòu)模式失敗的概率。
總結(jié)
- 上一篇: livy提交任务报错com.cloude
- 下一篇: scala中akka actor例子