微服務架構模式簡介

發表於2016-01-05

在2014年,Sam Newman,Martin Fowler在ThoughtWorks的一位同事,出版了一本新書《Building Microservices》。該書描述瞭如何按照Microservice架構模式設計及搭建一個具有良好擴充套件性並可持續開發的系統。除此之外,該書還將基於該模式的系統演化流程與Continuous Delivery等當前甚為流行的開發流程結合在了一起,使得Microservice架構模式看起來非常具有吸引力。基於這些原因,該架構模式迅速被業界所熟知,並在多個產品中被嘗試著使用。這其中就包含了我們公司的產品vRA。

在這一年多的時間裡,我們不但真正地體會到了Microservice所具有的一系列優點,也犯過一系列錯誤。因此在這篇文章裡,我會對Microservice架構模式進行簡單地介紹,並將我們所得到的經驗和教訓介紹給大家。

Monolith

網上對Microservice進行介紹的文章常常以Monolith作為開頭,我也不會例外。原因是,知道了Monolith的不便之後才能更容易地理解Microservice架構模式所具有的各種優點。

首先請回想一下我們所開發的服務是什麼樣子的。通常情況下,這個服務所對應的程式碼由多個專案所組成,各個專案會根據自身所提供功能的不同具有一個明確的邊界。在編譯時,這些專案將被打包成為一個個JAR包,並最終合併在一起形成一個WAR包。接下來,我們需要將該WAR包上傳到Web容器中,解壓該WAR包,並重新啟動伺服器。在執行完這一系列操作之後,我們對服務的編譯及部署就已經完成了:

這種將所有的程式碼及功能都包含在一個WAR包中的專案組織方式被稱為Monolith。在專案較小的情況下,這種程式碼組織方式還是可以接受的:更改完程式碼後,軟體開發人員可以趁著編譯器編譯程式碼的時候衝杯咖啡,並在回到座位後花費一分鐘部署剛剛編譯出來的WAR包以便測試自己剛剛所做的更改。但隨著專案的逐漸變大,整個開發流程的時間也會變得很長:即使在僅僅更改了一行程式碼的情況下,軟體開發人員需要花費幾十分鐘甚至超過一個小時的時間對所有程式碼進行編譯,並接下來花費大量的時間重新部署剛剛生成的產品,以驗證自己的更改是否正確。

如果應用的部署非常麻煩,那麼為了對自己的更改進行測試,軟體開發人員還需要在部署前進行大量的環境設定,進而使得軟體開發人員的工作變得繁雜而無趣:

從上面的示意圖中可以看到,在應用變大之後,軟體開發人員花在編譯及部署的時間明顯增多,甚至超過了他對程式碼進行更改並測試的時間,效率已經變得十分低下。

在變得越來越大的同時,我們的應用所使用的技術也會變得越來越多。這些技術有些是不相容的,就比如在一個專案中大範圍地混合使用C++和Java幾乎是不可能的事情。在這種情況下,我們就需要拋棄對某些不相容技術的使用,而選擇一種不是那麼適合的技術來實現特定的功能。

除此之外,由於按照Monolith組織的程式碼將只產生一個包含了所有功能的WAR包,因此在對服務的容量進行擴充套件的時候,我們只能選擇重複地部署這些WAR包來擴充套件服務能力,而不是僅僅擴充套件出現系統瓶頸的組成:

但是這種擴充套件方式極大地浪費了資源。就以上圖所展示的情況為例:在一個服務中,某個組成的負載已經達到了90%,也就是到了不得不對服務能力進行擴容的時候了。而同一服務的其它三個組成的負載還沒有到其處理能力的20%。由於Monolith服務中的各個組成是打包在同一個WAR包中的,因此通過新增一個額外的服務例項雖然可以將需要擴容的組成的負載降低到了45%,但是也使得其它各組成的利用率更為低下。

可以說,所有的不便都是由於Monolith服務中一個WAR包包含了該服務的所有功能所導致的。而解決該問題的方法就是Microservice架構模式。

Microservice架構模式

簡單地說,Microservice架構模式就是將整個Web應用組織為一系列小的Web服務。這些小的Web服務可以獨立地編譯及部署,並通過各自暴露的API介面相互通訊。它們彼此相互協作,作為一個整體為使用者提供功能,卻可以獨立地進行擴容。就以下圖所示的WikiPedia服務架構為例:

從上圖中可以看到,WikiPedia包含了一系列服務,如資料訪問服務Databases,搜尋服務Search等。這些服務都包含了數量不等的服務例項,以確保能在不同負載的情況下為使用者提供優質的服務。在使用者的請求到達時,它們將協同工作,一起完成對使用者請求的響應。

在使用Microservice架構模式的情況下,軟體開發人員可以通過編譯並重新部署單個子服務的方式來驗證自己的更改,而不再需要重新編譯整個應用,從而節省了大量的時間。同時由於每個子服務是獨立的,因此各個服務內部可以自行決定最為合適的實現技術,使得這些子服務的開發變得更為容易。最後如果當前系統的容量不夠了,那麼我們只需要找到成為系統瓶頸的子服務,並擴充套件該子服務的容量即可:

Microservice經驗談

以上就是對Miscroservice架構模式的介紹,是不是很簡單?實際上,這是一個正在發展的架構模式。在眾多討論中,關於該模式的標準實現,以及最佳實踐等眾多話題並沒有完全達成一致。因此我在這裡介紹的,是各個論壇討論中基本達成一致意見的一系列經驗。而各位在實現自己的Microservice架構模式時,一方面可以借鑑這些經驗,另一方面也可以根據專案本身需求調整Microservice架構模式的實現方法。

轉變你的視角

無論是在編寫一個服務,還是在編寫一個桌面應用,我們常常會首先嚐試將需要實現的功能分割為一系列元件,並圍繞著這些元件設計完成業務邏輯所需要的工作流及資料流。這種設計方法將導致實現業務邏輯的所有元件都執行在同一個程式之內,並且各個業務邏輯的實現也在同一個程式之內執行:

但是在Microservice架構模式中,我們需要更高一層的分割:在嘗試將需要實現的功能分割成為一系列元件之前,我們首先需要考慮如何將需要實現的功能交由彼此相互獨立的一系列服務來完成。例如在一個電子商務網站中,對使用者購買商品這一業務流程的支援就可以交由三個服務來完成:在使用者瀏覽商品的時候,其使用的是商品瀏覽服務;在使用者將商品新增到購物車並生成訂單的時候,其使用的是訂單服務;而在使用者進行網上支付的時候,其使用的則是付款服務。根據這種分割思路,我們的應用將執行在三個獨立的程式之中:

同時這三種服務各自的側重點並不相同:商品瀏覽服務中,對資料庫的讀操作比寫操作多得多,因此對讀操作進行優化將非常顯著地提高服務的執行效能;而訂單服務則是寫操作居多,因此我們需要對訂單的寫入效能進行優化;付款服務涉及到使用者的財產,因此其對安全要求會偏高一些。這種差異可能導致最適合實現這三個服務的技術各不相同。由於這些服務是完全獨立的,因此我們完全可以根據子服務的需求來決定所需要使用的技術,而不再需要考慮這些類庫是否與已有系統相容。

使用最合適的技術所帶來的優點就是,服務的程式碼會變得非常清晰明瞭,甚至在有些情況下可以達到簡潔優雅的程度。在一些討論中,有些人甚至建議一個服務只需要10到100行程式碼(他們常用簡寫LoC,即Lines of Code)。再加上服務已經獨立出來,而不再與其它服務混合在一起,因此正確地使用Microservice架構模式大大提高了程式碼的維護性以及新人上手的速度,也有助於技術人員在日常工作中進行技術集的更新及轉換。

但是這種對於服務的分割和元件之間的分割並不相同。最重要的一點就是在各個服務之間進行通訊的消耗相對於在同一個程式中而言是非常大的。在設計一個元件的時候,我們需要考慮該元件所給出的介面能夠儘可能地滿足當前及今後的一系列可以預見的需求,這便要求該元件所提供的API具有一定的前向相容性,並擁有一系列其它特性,如靈活性,擴充套件性等等。這常常導致該元件所提供的API具有較細的粒度。在程式執行時,對該元件所提供的API的呼叫就在當前程式中進行,速度非常快,因此頻繁地對該細粒度API進行呼叫並沒有太大的問題。但是一個跨服務呼叫所需要的時間則比程式內呼叫的時間長很多。如果在處理一個請求的時候需要太多的跨服務呼叫,那麼整個應用的效能將變得無法忍受。因此我們在執行服務分割時定義的API需要是粗粒度的API。

就讓我們以一個電子商務網站為例。在為使用者生成訂單時,電子商務網站常常需要列出各個商品的主要資訊,商品的價格,優惠幅度,並通過庫存系統檢驗該商品的庫存,從而得到整個訂單的內容。如果每次與其它服務溝通都需要100毫秒,而且整個訂單包含了20件貨物,那麼系統準備訂單的時間就會達到8秒(100ms * 4次呼叫 * 20件商品)。這從使用者的角度來說是不可以接受的效能。而且隨著訂單中所包含商品數量的增多,系統準備訂單的時間會線性增長,進而使得系統的效能更加不可忍受。

究其原因,實際上還是因為準備訂單所呼叫的API的粒度太細了。如果訂單系統能夠一次性地把一件商品的主要資訊,價格,優惠幅度以及庫存資訊從商品服務中取回來,那麼其效率就將提高四倍。如果訂單系統不需要為每件商品依次傳送請求,而是可以通過一次性地服務間呼叫就能取回所有需要的資訊,那麼系統準備訂單的時間將不會再隨著訂單的增大而增長。因此在Microservice架構模式中,各個服務應該提供可以被靈活使用的粗粒度API,以減少各種跨服務呼叫的消耗。

除了各個服務所提供的API的粒度,服務分割的粒度也是在服務分割過程中需要考慮的因素。如果一個服務的粒度太小,那麼它所提供的API的粒度也不會高。一個較為普遍的看法是,在Microservice架構模式中,一個服務需要能夠獨立地完成特定的業務邏輯,至少是某個獨立資源的CRUD操作。例如在電子商務網站中,我們需要一個服務能夠獨立地完成對商品相關資訊的讀取,如商品的主要資訊,商品的價格,參與的優惠活動等。

這裡有一個例外,那就是公共功能的處理。試想在一個應用中,我們常常需要一個許可權管理元件來管理使用者所具有的各個許可權。許可權管理元件常常實現了一種公用的安全模型(Security Model),如ACL(Access control list),RBAC(Role-based access control)等。在每次訪問一個子服務的時候,這些服務都需要檢查使用者所具有的許可權:

發現問題了麼?是的,每次對一個產品系統及訂單系統的呼叫都需要從許可權系統中得到當前使用者的許可權,才能決定使用者是否能夠訪問特定資訊。如果這樣的公共服務很多,那麼該系統的效能將會變得非常差。

解決該問題的一種方法就是在各個系統中將下次還能夠使用的資訊快取起來,也就是在這些系統中為使用者建立一個會話。由於每個系統可能由多個服務例項所組成,為了能夠重複利用會話中所儲存的資訊,減少向公共服務傳送請求的次數,我們需要通過負載平衡技術讓系統中的同一個服務例項處理同一個使用者的請求。有關如何實現該功能,請見我的另一篇文章《企業級負載平衡簡介》。

除了效能問題之外,公共服務還會與各個服務產生一種邏輯上的依賴關係。讓我們繼續通過許可權系統這個例子進行討論。當許可權管理的組成存在於各個服務中的時候,我們可以直接通過傳入使用者的資訊以及需要訪問的資源就能判斷出到底使用者是否能夠訪問特定資源。也就是說,從許可權管理組成所返回的實際上就是一個布林型別的資料。但如果許可權管理不再是一個組成,而是一個服務,那麼為了避免每次都呼叫許可權管理服務,我們需要在使用者的會話中記錄使用者所具有的許可權。在使用者下次訪問該服務的時候,我們可以通過直接檢查該使用者所具有的所有許可權就能決定其是否能夠訪問特定資源了。這些在使用者會話中記錄的許可權常常具有其特定的表現方式,例如特定形式的字串,而這種字串表示需要同時被服務和許可權管理服務所理解,從而造成了這兩個服務之間的耦合:

但是這種方式為子服務增強了對許可權系統的依賴性。和元件之間的耦合一樣,增大的耦合性會導致服務的重用性下降。

所以說,如何對服務進行分割實際上是Microservice架構模式中最需要技巧的事情。在分割過程中,服務的總體效能是至關重要的,而各個服務的獨立性也是大家所最為關心的特性。當然,Microservice架構模式仍在逐漸發展中,因此相信會有越來越多的實踐經驗被大家所發掘出來,進而指導我們更好地對服務進行分割。

共享服務

在前面一節中,我們已經提到了公共服務。實際上,這是Microservice架構模式中最需要技巧的一部分。

實際上,Microservice架構模式實現中常常需要一系列公有服務以輔助整個應用的執行。除了我們剛剛提到的許可權管理服務,我們還需要能夠監控各個服務例項的服務狀態,服務例項的新增刪除升級管理等等。這些服務在各個子服務的服務例項之間共享,甚至可以在其它應用中被重用。

只是很多人擁有一個這樣的誤區,那就是Microservice架構模式可以讓服務的開發變得更容易。而實際情況則恰好相反。在剛開始使用Microservice架構模式開發應用的時候,其效率是明顯低於通過Monolith進行開發的:

從上圖中可以看到,在剛開始的階段,使用Microservice架構模式開發應用的效率明顯低於Monolith。但是隨著應用規模的增大,基於Microservice架構模式的開發效率將明顯上升,而基於Monolith模式開發的效率將逐步下降。

為什麼呢?這是因為Microservice是一個架構模式,而不是一個特定的技術解決方案。其並不會將開發中的各個難點全部轉移,而只是允許通過更為合適的技術來適當簡化單個子服務的開發,或者繞過開發中可能遇到的部分難點。但是為了支援各個子服務的執行,我們還需要建立一系列公共服務。這些公共服務需要在編寫第一個子服務的同時進行。這是導致Microservice架構模式在開發初期會具有較低效率的一個原因。

然而使用特定技術並不會繞過開發中所能遇到的所有難點。由於在Microservice架構中,各個子服務都集中精力處理本身的業務邏輯,而所有的公共功能都交由公共服務來完成,因此公共服務在保持和各個子服務的鬆耦合性的同時還需要提供一個足夠通用的,能夠在一定程度上滿足所有當前和未來子服務要求的解決方案。而這也是導致Microservice架構模式在開發初期會具有較低效率的另外一個原因。

而在開發的後期,隨著Monolith模式中應用的功能逐漸變大,增加一個新的功能會影響到該應用中的很多地方,因此其開發效率會越來越差。反過來,由於Microservice架構模式中的各個子服務所依賴的公共服務已經完成,而且子服務本身可以選擇適合自己的實現技術,因此子服務的實現通常只需要關注自身的業務邏輯即可。這也是Microservice架構模式在後期具有較高效率的原因。

當我們再次通過Microservice架構模式搭建應用的時候,其在開發時的效率劣勢也將消失,原因就是因為在前一次基於Microservice架構模式開發的時候,我們已經建立過一次公共服務,因此在這個新的應用中,我們將這些公共服務拿來並稍事改動即可:

從上圖中可以看到,雖然我們仍然需要花一些時間來對公共服務進行一些修改,但是此時所導致的效率下降已經不再那麼明顯了。也就是說,就算是在前期,我們已經擁有了較高的開發效率。

而且隨著Microservice架構模式的不斷流行,在網路上會有越來越多的使用者共享自己的公共服務解決方案。那麼第一次按照Microservice架構模式編寫應用所導致的效能下降也會逐漸變得越來越小。

模型匹配

OK。在介紹了共享服務之後,我們就可以討論Microservice架構模式中的另外一個問題:模型匹配了。在Microservice中,各個服務是彼此獨立的,而且是關注於自身業務邏輯的。因此在看待一個事物的時候,Microservice可能擁有不同的視角,進而造成了各個子服務中的對應模型並不匹配。

例如在一個IaaS雲中,一個使用者所具有的角色可能會根據他所擁有的職責來劃分:雲上擁有一系列用於監控的使用者,用來完成對雲的整體執行監控等工作(不包含檢視使用者資料,這是個安全問題)。同時雲上的使用者又可以分為帳號管理員,Tenant管理員,資源管理員以及普通使用者等。而在其上執行的應用即服務(AaaS,Application as a Service)中,其使用者的職責劃分可能是另一個樣子:在AaaS上定義應用的是應用架構師,負責應用部署及維護的則是運維人員。在應用架構師設計一個應用的時候,其並需要擁有IaaS雲上訪問資源的許可權,卻並不需要分配資源的許可權,但是運維人員需要擁有該許可權以對應用進行部署和維護。

也就是說,IaaS雲中的許可權劃定和AaaS服務中的許可權劃定並不一樣。通常情況下,我們常常在業務整合時執行一次對許可權的匹配:

從上圖中可以看出,由於AaaS服務是執行在IaaS之上的,因此為了能夠操作IaaS中所包含的各個資源,AaaS服務需要將自己的使用者角色匹配到IaaS所定義的角色上。例如應用架構師需要能夠在定義應用的時候需要知道IaaS上所具有的資源,並需要能夠指定到底哪些人可以使用這些應用,因此其需要擁有IaaS的Tenant管理員,資源管理員及普通使用者三種角色。而AaaS上的運維人員則只需要在部署和維護時察看IaaS上所擁有的資源,因此其只需要資源管理員及普通使用者兩種角色。

但是這麼做有兩點不好的地方:如果Microservice中只包含了幾個服務,而且這種服務之間的依賴關係並不是很多,那麼這種服務匹配還能夠解決,但是如果整個系統之間各個子服務的溝通很多,那麼在各個子服務之間進行角色匹配將變成一個噩夢:

解決該問題的方法就是使用我們上節所介紹的公共服務對它們進行管理。在提供一個集中的公共服務的情況下,我們就不再需要處理這麼多的模型轉化了:

除此之外,僅僅簡單地對角色進行匹配實際上並不那麼合適:就應用架構師而言,其需要的是檢視當前的已有資源,卻不需要對資源進行分配。因此其需要的是對資源的讀許可權。而運維人員則不僅僅需要能夠讀取資源資訊,更需要對資源進行分配,因此其需要的是資源的讀寫許可權。如果僅僅像上面那樣在IaaS層為應用架構師賦予對資源的讀寫許可權,那麼應用架構師就可能擁有了錯誤的許可權,進而執行了錯誤的操作。而相對地較為合適的方式則是對這些許可權進行細分,即在許可權中區分讀寫許可權等:

因此在集中的公共服務中,我們需要使用較為細粒度的模型。該細粒度模型需要具有較高的靈活性,以能夠無損地表示各個服務中的相應模型。

相信您現在已經能夠看出,雖然說Microservice架構模式將單個子服務的實現簡化了,但是複雜化了資料的處理。因此相較於我們以往所編寫的應用,Microservice架構模式會在資料相關的一些特性上遇到一系列麻煩。

一個較為常見的麻煩就是保持多個子服務之間資料的一致性。我們知道,在服務中,保持一致性的工作常常是由事務來完成的。而如果希望在Microservice架構模式實現中保持子服務之間資料的一致性,我們可能就需要使用分散式事務了。但是分散式事務本身就是一個非常複雜並且難以操作的東西,因此就現在而言,這種問題實際上是非常難以解決的。但是反過來講,事務本身也是表示一種邏輯上的強耦合,因此我們需要真正反思的則是這些需要使用事務來保持資料一致性的子服務是否應該屬於同一個服務。當然,我們可以在某種程度上借鑑NoSQL資料庫中的一些做法。例如在一個服務更新了資料以後,我們使用一種非同步機制來保持資料的一致性,就好像很多NoSQL資料庫不保證使用者的資料立即可讀一樣。

另一個較為常見的麻煩就是粒度的問題。我們在前面已經說過,在Microservice的各個子服務之間進行服務間呼叫效率是十分低下的。為了減少多次服務間呼叫,各個子服務所提供的API的粒度需要儘量地粗,卻需要儘量地保持靈活性。最好的情況就是可以通過一次服務間呼叫來得到所有想要的資訊。

專案管理

除了上面所討論的一系列技術因素之外,Microservice架構模式的開發還存在著一系列專案管理上的難題。

首先,由於Microservice架構模式中的各個子服務可能使用了不同的技術搭建,例如有些子服務是由Java開發的,有些則是由Python開發的,而且它們所使用的Servlet容器並不相同,因此由Microservice架構模式所搭建的應用可能需要非常複雜的環境設定。這對於傳統的運維人員來說是非常困難的一個任務。而相對於這些運維人員而言,負責各個子服務開發的開發人員才是有關該服務執行及部署的專家。因此在Microservice架構模式中,開發及運維的職責均發生了變化:開發人員不僅僅需要負責子服務程式碼的編寫,還需要考慮該子服務的日常運維。而運維人員需要向開發人員給出一些運維相關的建議,並在總的方向上掌控產品的日常運維。

這樣做的好處則在於:開發人員會直接接觸到生產環境,可以快速地跟蹤並解決問題,而不再需要通過客戶及運維人員的轉述等步驟才開始處理問題,也避免了在轉述過程中出現的偏差。除此之外,開發人員也能更清楚地瞭解使用者到底是如何使用他們所建立出來的產品的,進而建立出來更容易被使用及管理的子服務。

但是這也會導致專案管理出現一些困難。首先,不論是開發人員還是管理者都需要了解並處理一系列運維相關的問題。這會分散他們的注意力,使得開發效率的降低。其次,由於一個子服務常常同時包含前端,後臺,資料庫,測試,甚至運維相關的一些任務,因此子服務的開發人員常常需要了解服務開發的大部分組成。這種人才在中國市場上並不多見,因此比較搶手。而且由於一個開發人員需要接觸太多的功能和技術,因此很多時候沒有辦法深入地研究它們。由此所導致的問題則是,在遇到較為困難的問題時,軟體開發人員需要花費較多的時間來分析並解決該問題。如果該問題較為嚴重,那麼它將會嚴重影響整個組的開發進度。從專案管理的角度來講,這實際上是一件非常危險的事情。

一個理想的解決方案就是,當前子服務所使用的各個技術都有一個專家。但是一個全棧開發人員,還需要是某一方面的技術專家,僱傭該人的成本可想而知。

除此之外,我們還需要在按照Microservice架構模式開發的時候使用一系列標準化的開發及測試流程。其中和Microservice最自然契合的就是現在最為流行的Continuous Delivery,或被稱為是DevOps。在這些自動化流程的幫助下,軟體開發人員可以快速地完成一次迭代:在對程式碼更改完畢以後,軟體開發人員可以直接開始對自己的更改進行編譯,執行單元測試及功能測試。接下來,系統將會把剛剛編譯好的程式碼自動進行部署,並在整個系統中執行整合測試。在整合測試完畢之後,質量管理人員或軟體開發人員自己會在該系統中進行一次測試,並在完成測試後進行復雜的效能測試,並在通過效能測試後進行部署。

所有這一切實際上都和使用Monolith開發時所使用的流程類似。唯一不同的是,在基於Microservice架構模式的開發中,這種自動化的流程變得更為重要了。因為基於Microservice架構模式所搭建的應用常常使用了不同的邏輯,因此部署一個完整的環境就會變得非常複雜。所以由這些自動化流程來負責測試環境的部署則大大地減輕了軟體開發人員的負擔,也是提高軟體開發人員工作效率的基礎。

同時由於軟體開發人員需要隨時執行應用程式的部署來測試自己剛剛所做的更改,因此其需要能夠隨時分配到其所需要的各個資源,如部署應用所需要的計算資源,記憶體以及儲存等。而這種功能則正是雲這種商業模式所提供的功能。因此在開發基於Microservice架構模式的應用時,我們則儘量基於某些雲來開展我們的持續開發流程。

Microservice實現

在本節中,我們將對實現Microservice架構模式時所常用的一些方法進行講解。

相信大家的第一個問題就是,Microservice架構模式中各個子服務應該如何相互協作以向使用者提供服務的呢?按照上面我們的講解,Microservice架構模式中各個子服務應該是獨立的,否則它們之間將產生耦合,進而帶來一系列問題:這些子服務彼此不獨立,需要使用分散式事務保持其資料一致性,子服務不易被重用等。但是如果這些子服務絕對獨立,甚至不包含一點點邏輯上的耦合,那麼它們之間也將無法進行協作。因此在論壇討論中常常出現的問題就是,這些子服務之間哪裡可以出現耦合?可以出現什麼程度的耦合?

這個問題實際上非常簡單,那就是UI。我們知道,在一個BS服務中,服務端和客戶端之間存在著一定程度的耦合。兩者通過服務所暴露的API進行溝通。而基於Microservice架構模式的服務也不例外:

既然執行在使用者瀏覽器中的UI需要與其它各個子服務進行互動,那麼它完全可以作為一箇中介者來完成各個子服務之間的互動。例如在顯示產品頁面的時候,該頁面邏輯會向產品服務及庫存服務同時傳送請求,以並行地得到產品的詳細資訊以及該產品的當前庫存。

因此在一個基於Microservice架構模式的服務中,常常會出現一個前端服務。該服務所提供的頁面會與各個服務溝通。但是它實際上與各個子服務之間卻不需要通訊:

或許您會說,在這種情況下,我們的各個子服務就沒有UI了。而UI服務不僅僅需要處理所有的前端業務邏輯,而且隨著時間的推移,其可能會變成另外一個龐然大物。除此之外,如果希望整個平臺能夠允許第三方服務接入,那麼這種打包在一起的UI服務將變成整個平臺擴充套件性的阻礙。

是的。如果需要解決這個問題,那麼您就需要在應用中嘗試借鑑Service Locator模式。此時我們需要的則是一個UI框架,其允許使用者通過特定方式在應用中插入各個子服務所提供的UI,並允許您通過一些機制來發現已經在平臺中註冊的具有特定功能的API,並允許您對該API進行呼叫。我相信,隨著Microservice架構模式的不斷髮展,會有越來越多的支援這種擴充套件方式的UI類庫出現。

另外一種模式則是Message Broker。簡單地說,Message Broker就是一個訊息的中轉平臺。該平臺允許其它組成向其中註冊訊息,也允許其它組成偵聽訊息。當一個組成將一個訊息傳送到了Message Broker之上後,其它偵聽該訊息的各個組成則會根據訊息中所包含的資訊更新自己的狀態。

反過來,如果您的服務需要支援移動裝置,如手機,iPad等,我們就不能讓這些移動裝置一個一個地訪問子服務了。這是因為這些移動裝置的頻寬一般來說都非常小,而且使用者常常處於訊號不是很好的地方,因此在向這些子服務一個個地傳送請求將快速消耗掉它們所擁有的有限的頻寬。為了解決這個問題,我們常常需要在這些子服務前搭建一個代理服務。該代理服務會將使用者請求根據業務邏輯拆分為對各個子服務的請求,並將各個子服務所返回的結果歸納為一個響應返回給使用者:

當然,上面所介紹的僅僅是當前論壇討論中所常常提到的一種搭建基於Microservice架構模式應用的方式。或許在不久的將來,您會看到設計得越來越精巧的各種模式出現。

在講解完這些子服務該如何展現給使用者之後,我們就來講解一下如何建立各個子服務所需要的公共服務。之前我們已經提到過,由於對公共服務的呼叫是一個跨程式呼叫,因此其相較於程式內呼叫效率非常低下。在這種情況下,我們需要儘量避免對該公共服務的重複呼叫。為了達到該目標,我們需要儘量使使用者訪問同一個子服務例項,並且在該使用者的會話中快取從公共服務中所得到的資訊。

因此在同一個子服務的各個服務例項上,我們需要儘量使用負載平衡服務的Sticky Session的功能,並在一次公共服務呼叫中取得多項資訊。例如在檢視使用者的許可權時,我們不是返回使用者是否具有特定許可權,而是該使用者擁有哪些許可權。當然,這不僅僅需要從Microservice這種架構模式的方面來考慮,還需要同時兼顧安全,維護性等一系列問題。

簡單地說,在兼顧其它方面的情況下,我們需要將公共服務API的粒度定得粗一些,同時也需要具有一定的靈活性,從而通過減少服務間呼叫來避免整個服務的效能瓶頸。

既然說到了API的粒度,那我們就需要討論一下各個子服務所提供的API了。和公共服務一樣,各個子服務所暴露的API也應該具有較粗的粒度以及較大的靈活性。除此之外,我們還需要讓這些子服務所暴露的API具有儘量一致的樣式,如定義一系列RESTful的API。在這種情況下,與這些服務進行互動的組成,如網頁的UI,才能具有可以接受的維護性。

一個經驗性的觀點則是,Microservice架構模式中的“開”是各個服務的內部實現,而其中的“閉”則是各個服務之間相互溝通的方式。

如果您需要從頭開始搭建一個服務,那麼您需要首先考慮如何對這些服務進行劃分,並在建立第一個服務的時候開始搭建出各個公共服務的雛形,同時確定各個服務之間溝通所需要遵守的協議。當越來越多的子服務被建立出來之後,您需要逐漸豐富各個公共服務所提供的功能,使其逐漸變為功能強大的,可重用的服務。

如果您已經擁有一個Monolith服務,並且希望通過採用Microservice架構模式來緩解當前Monolith模式服務所具有的一系列問題,那麼您首先需要建立一個獨立的服務,並通過一個粘合層來與該Monolith服務互動。在該過程中,您可能需要將Monolith服務的內部介面逐漸暴露出來,以供這個新的服務使用。而這就是在抽象公共服務的過程。

接下來,您就需要根據上一步中所得到的介面來逐步將Monolith服務中的公共服務剝離。在剝離過程中,您腦中需要記得的一句話還是:粗粒度,靈活的API。而其內部實現到底是什麼樣的,實際上並不會影響到您的剝離結果。

最後就是再從Monolith中剝離其它服務了。此時我們最需要考慮的就是在服務中具有鮮明特點的各個服務,如對資源的要求與整個Monolith服務格格不入,或者使用了和Monolith很難相容的技術等。

最後一種情況就是多個服務整合的情況。在產品的逐漸迭代過程中,我們常常會遇到需要將多個產品整合成為一個產品以提高整體競爭力的情況。這常常發生在盈利產品和其它非盈利產品之間。而這正是實踐Microservice架構模式的絕佳機會。此時我們僅僅需要暴露一系列Monolith服務中的介面並建立粘合層即可。

Microservice的優點與劣勢

好,在前面我們已經講解了很多有關Microservice架構模式的經驗性方法和相關知識。那我們現在回顧一下Microservice所具有的一系列優點和劣勢,以使您能夠在採用Microservice架構模式之前全面地衡量該方案所可能得到的好處及遇到的困難。

首先,由於Microservice架構模式中的每個子服務都可以獨立於其它服務執行,因此其常常具有更好的服務邊界。而這個明確的服務邊界則會帶來一系列好處:在Microservice架構模式中,各個子服務執行所需要的業務邏輯都相對集中於子服務內。因此其實現程式碼相對容易理解,並且便於維護。另外各個子服務所具有的結構,執行流程及資料模型都能夠更貼近於子服務所表示的業務邏輯,因此在程式碼的開發速度和維護性上得到了大大地增強。同時各個子服務可以選擇最適合實現業務邏輯的技術,進而使得各個服務的開發變得更為容易。同時在出現新的更適合的技術時,我們可以較為容易地在各個子服務內部對原有的實現技術進行替換。

獨立性也意味著擴充套件性的增強。在Microservice架構模式中,各個子服務可以根據自身的負載獨立地進行擴容,如Scale Up或Scale Out等。不僅如此,我們還可以根據子服務自身的特性為其準備特定的硬體裝置,使得其執行在更適合的伺服器上。同時這種獨立性還可以使得各個子服務可以被重用。

同時這種獨立性也可以增加整個服務的容錯能力。例如如果一個子服務由於種種原因無法繼續提供服務,其它子服務仍然可以獨立地處理使用者的請求。

另外,各個子服務的獨立部署能力也可以大大地提高Continuous Delivery的執行效率。畢竟在這種情況下,軟體開發人員只需要重新部署更改過的子服務就可以了。

由於Microservice架構模式中的各個子服務無論是在程式碼量方面還是最終生成的WAR包方面都較Monolith架構所搭建的服務小,因此在IDE支援,啟動速度方面都具有相當的優勢。同時,這種小粒度的服務已經可以由一個幾個人所組成的小組來完成,而不再需要通過來自世界各地的不同小組協同開發,進而大大降低了溝通成本,提高了開發的效率。

但是反過來,Microservice架構模式中各個子服務的獨立性也會導致一系列問題。最明顯的就是需要多個子服務相互配合的情況。由於這些子服務是不同的程式,因此在這些程式之間保持資料的一致性,或新增一個新的跨子服務的使用者用例實際上都是一件非常麻煩的事情。而且對這些獨立服務在整個系統中是否能夠工作的測試需要執行大量的整合測試。而如果需要快速地對這些子服務進行開發和迭代,那麼我們就需要每個開發人員都能夠專業並高效地使用一系列自動化工具。這實際上也是一個不低的要求。

除此之外,基於效能考慮,各個子服務所提供的介面將是粗粒度的,卻具有較高靈活性的API。但是這種API擁有一個較明顯的缺陷,那就是越靈活的API,其使用起來的難度就越大。因此對於服務的使用者而言,其上手的難度則相對增加了。

另外,如何規範化各個子服務之間的溝通協議也是一個非常具有挑戰性的事情。因為在Microservice架構模式中,我們常常需要建立一系列公共服務。這些公共服務常常暴露特定樣式的介面以供其它服務呼叫。因此我們需要在這些介面上保持一致性,進而才能夠更自然地編寫各個子服務的內部邏輯並暴露適當的介面。但是反過來,一致的介面樣式常常會導致各個服務的自然實現需要向這些標準進行妥協。因此我們常常需要在兩者之間平衡。

這些平衡方法包括標準化各個服務所暴露的介面,使用固定的幾種方式對子服務進行整合,保持資料模型格式的一致性等。這些實際上都是我們自由編寫各個子服務的障礙。對此採取多麼嚴格的規範實際上是需要通過經驗累積來完成的,因此這大大提高了使用Microservice架構模式失敗的概率。

相關文章