淺談微服務與網站架構的發展史
概要
架構的觀念在我們開始學程式時就已經接觸,只是對多數的初階程式設計人員來說,架構的意義過於模糊,當時的我們所關注的更多是語法的正確性或者是「API」的使用方式⋯等,但僅能說我們沒有關注「架構」,而不代表我們與它無關,事實是我們始終都在接觸著架構,例如物件導向的概念、設計模式、「SOLID」、「GRASP」⋯等。
架構種類很多,如本文的主角:「單體式架構」和「微服務架構」,又或是耳熟能詳的「前後分離架構」、「主從式架構」,又或是經常被一起討論與比較的「MVC」、「MVP」、「MVVM」設計架構。
雖然它們都是架構,但不見得都是同一回事,畢竟不是所有架構關注的面向都是同樣的,更可能其關注的問題本身就不屬於同個層級,因此,架構與架構之間的關係並不一定都是衝突的,它們或許真的是對立、競爭的關係,但也可能是互補、依賴。
那麼在要學習架構之前,我們先來了解架構的「前世今生」。
正文
單體式應用程式(Monolithic Application)
單體式應用程式,又稱為「整體式應用」,意即將網站的所有功能、模組,包含邏輯、服務,甚至是畫面,都集中於一個應用程式中,是種「大雜燴」的概念。
以一個含有「商品資訊」、「會員中心」與「購物車」⋯等項目的電商網站為例;在「單體應用架構」的設計理念中,其會將所有業務模組集中於一個應用內,並以一個整體為單位進行維護、管理與部署⋯等,圖如下:
於上述架構中,「商品資訊」、「會員中心」與「購物車」等項目,通常會是以「Service」的形式存在,在同一「目錄」或「Package」下,以本地端接口的形式被呼叫、使用,並集中部署,非獨立、程式碼相依性極高且耦合緊密;但對於一定規模以下的專案來說,其更易於修改、管理與維護,部署的成本也比較低。
但是對於規模較大的專案來說,「高耦合」所導致的問題也會隨之顯現;即所謂的「牽一髮動全身」,由於程式碼彼此關聯、依賴,使得當我們因為需求要修改程式碼時,就必須連其牽動的部分一併修改,甚至會牽扯到架構的改動,不僅傷筋動骨,還可能破壞程式碼原有的一致性。
此外,「高耦合」除了讓程式彈性變差,不易擴展外,還有一個很關鍵的問題,就是不利於「多人協作」的,別的不說,僅就版控而言:「聽說過一句話嗎?衝突的話,慢的那個負責『Merge』!」。
這很嚴重嗎?這個⋯,遇過就知道。
事實上,「多人協作」的衝突管理問題一直都是實務上的痛點,隨著數位化時代的來臨,網站的開發越發注重效率,並且需求相對複雜,尤其是特定領域的應用,如「電商」、「金融」⋯等,其「單兵作戰」早已成為過去式。
總之,隨著時代的發展,「單體應用架構」已經逐漸不適用於所有的網站開發,所以才會有所謂的「微服務架構」出現。
其實「單體式應用程式」這個名詞是在「微服務」盛行後才常被提起並拿來與之比較的;而與之對應的應該是「單體應用架構」,之所以這麼說是因為在網站發展早期根本沒有什麼「分布式系統」的概念,所有網站都是「單體應用程式」。
分布式系統,乃至於微服務架構都是最近幾年才比較盛行的架構設計模式。
故此,「單體應用程式」在網站架構史上發展了相當長的一段時間,但就如同先前所說的「單體應用程式」是個「大雜燴」,其緊密耦合的問題一直困擾著所有的程式開發人員,為此,人們陸續提出解決方案,譬如對程式碼結構分層,例如「MVC」、「MVP」、「MVVM」⋯等,其中,最廣為人知的模型就是「MVC」,它的概念就是將程式碼分為三大結構,分別是「資料模型(Model)」、「視圖(View)」和「控制器(Controller)」,如下:
但不論「MVC」,或是其衍伸的「MVP」或是「MVVM」,其本質都還是一個「應用程式」,即所謂的「單體式應用程式」。
前、後端分離
在網站架構發展的過程中,其中有一個相當重要的轉捩點,就是「前、後端分離」的架構模式逐漸成為主流。
在還沒有「前、後端」觀念之前,「UI」部分的處理同樣是由網站負責,事實上,這時期網站的畫面邏輯相對簡單,網站會藉由「Response」將網站頁面的「HTML」回傳,並直接顯示於網站上,如下:
但隨著科技的發展,用戶端設備的運算能力逐漸強大,並足夠勝任更複雜的畫面處理,因此,「前端」的概念開始出現;此時,瀏覽器不再只負責呈現畫面,它開始能夠運算,所以,我們開始將畫面邏輯的相關處理放到瀏覽器上。
此外,更由於「畫面邏輯處理」的轉移,我們已經不需要在「網站」上進行有關畫面邏輯的運算,我們只要將單純的數據資料交付給予「前端」即可,因此,「Response」中所回傳的也不再是用以描述畫面的「HTML」,而是更單純的數據格式,如「JSON」⋯等。
另外,既然傳輸的是單純的數據,無關於畫面的處理,因此,跨平台也就跟著實現,如下:
分散式系統(Distributed System)
與「單體式應用程式」對立的就是「分散式系統」。
若提到「分散式系統」,基本上提起的通常是「分布式架構」,或基於它之後所發展的架構;但是在它之前,還有一個「垂直應用架構」。
先前我們曾說過「單體應用架構」不利於「大規模網站的開發」;但問題是大規模網站的需求就是存在,那怎麼辦呢?「垂直應用架構」就是一開始被提出來的解決方式。
它的作法就是將單體式應用根據特定面向進行拆分,如「業務項目」、「客群」⋯等;「業務項目」就如上述例子中的「商品服務」、「訂單服務」以及「商品服務」⋯等;而「客群」就好比「一般戶」、「企業戶」⋯等。
要注意的是,拆分後的單位是「系統」,是相互獨立的系統,如下:
經拆分後,原本「單體應用架構」不利於大規模網站開發的兩大因素:「程式彈性差、不易擴展」與「多人協作困難」,雖不能說完全解決,但至少都得到一定程度的紓解。
題外話,雖然「垂直應用架構」已經初步具有「分散式系統」的概念,但根據筆者觀察,不少書籍仍將之視為「單體式應用程式」的衍伸,而並非歸類在「分散式系統」,當然也有不少書籍將其獨立為一個項目,呃,就各憑所好,筆者在此就將之歸類為「分散式系統」吧。
言歸正傳,雖然「垂直應用架構」改善了「單體應用架構」的痛點,但同時又產生出新的問題,其中最讓人詬病的問題是「重覆造輪子」,即大量重工的現象;這是因為網站拆分成系統後,其不論拆分依據為何,都一定有原本架構中「共用的部分」,但問題是在「垂直應用架構」中的各系統是「無法直接交互」的,所以其原本「共用的部分」就必須在各個系統中重刻一份。
另外,除了「重覆造輪子」的問題外,還有另一個問題,就是「系統間訊息傳遞不易」。
雖說系統間彼此獨立能夠有效地「解耦合」,但不同系統間必定會有資訊傳遞的需求,例如使用者要將「商品系統」中的某一商品項目加入到「訂單系統」中的「購物車」內;是的,或許有人會說,我們可以藉由「資料庫」作為溝通橋樑,是的,可以的,但稍微思考一下就會發現,這並不合理。
倘若網站的所有資料全仰賴「資料庫」傳遞,先不談論是否會需要撰寫過多冗餘的程式碼,僅單是「資料庫的負載力」就是個大問題了,更何況我們還必須考量資料傳遞間的「即時性」、「一致性」⋯等因素。
在網站架構的演化上,「分布式應用架構」是一個相當重要的里程碑。
與「垂直應用架構」相比,「分布式應用架構」是真正意義上的實現系統間遠端通訊。
而關鍵就是「遠端程序調用(RPC)」。
試想,在過去我們要如何讓兩隻遠端程式相互溝通呢?
我們可以將資料「序列化」,再經由各種不同介面或協定來傳輸,常見的傳輸協議如「HTTP」、「TCP」、「UDP」、「FTP」⋯等,如下:
而「RPC」就是這段過程的「通訊協議」,它的目的就是:「讓調用遠端程式時的操作就像是在調用本地端的程式一樣。」,如下:
圖片來源為「Java RPC」。
當然,它並不是真的做到直接將遠端程式「import」並呼叫,而是透過設計來模擬本地方法暴露的情況。
事實上,為了達到「遠端調用」目的,「RPC 的模型」可能還是會涉及有關於「二進制編碼」、「序列化」或是「XML」⋯等機制,而其網路間的傳輸協議也仍是「TCP」、「UDP」、「HTTP」⋯等,其圖如下:
實際上,「RPC」協議並沒有對實作模型有限制,所以上圖僅是其中的一種例子,更詳細的介紹可以參考「IBM Remote Procedure Call」。
題外話,以「微服務架構」來說,若提到服務間的通信,其更被常提到的名詞是「Restful API」,那「RESTful API」與「RPC」的差異在哪?
起初,筆者以為「RESTful」與「RPC」是兩種同層級的平行概念;但實際上,並不是的,兩者其實是不同的概念;「REST」是一種基於協議上的設計風格,如「HTTP」、「URI」、「XML」⋯等,但它本身並不是一種標準,它是面向資源,關注接口規範;而「RPC」是面向程式方法設計,用於解決複雜的遠端通訊協議;實際上,這個問題在「StackOverFlow」也曾有過相關的討論:「Web service differences between REST and RPC」。
言歸正傳,先前筆者曾說,基於「RPC」的實現,使得「分散式系統」中的各遠端系統得以相互溝通,也因為如此,我們便可以把各系統中的共用部分抽走,建立成共用服務,圖如下:
如此一來,「垂直應用架構」的兩大痛點:「重覆造輪子」和「獨立系統間訊息傳遞不易」就有效地被解決了;此外,「單體應用架構」的問題:「不利於多人開發」和「程式彈性差、不易擴展」,在「分布式架構」中同樣得到了改善。
似乎一切都很美好,但真的就沒問題了嗎?
試想,當專案規模逐漸擴展,項目分拆越來越多,依賴越發複雜時呢?情況大概就會如下圖:
圖片資料來源:「ESB和SOA到底是什么?」。
系統與服務之間的關係將越發錯綜複雜,耦合度變高,然後彼此關係難以疏理,最後走向無法維護,是吧?
事實上,除上述問題之外,由於該將架構缺乏統一管理,在資源調度上也會出現問題,尤其,若當某服務負荷過大,我們想以分流設計時,那系統層要如何導向?其依據為何?
服務導向架構(Service-Oriented Architecture, SOA)
因此,為了解決「分布式應用架構」所衍伸的資源調度與難以管理的次序依賴關係,所以又一個新的概念被提出來:「服務導向架構(SOA)」。
嚴格來說,「SOA」並不是一種技術,而是一種設計方法、一種概念。
根據「IBM」的「What is SOA?」文中描述,「SOA」的目標就是「定義一種透過『服務介面』讓軟體元件『可以重複使用』的方式。」,如下:
更直白地說,就是在各項服務之間建立一個「整合性介面」,然後由它統籌資源的調度與分派,可以參考「Service-oriented architecture (SOA)」,概念圖如下:
而「SOA」架構中,最關鍵的元件就是「整合性介面」,而「ESB」就是其中的一種實現的方式。
那何謂「ESB」呢?在「IBM」的「ESB(企業服務匯流排)」一文中是這樣敘述的,如下:
與「ESB」同樣為「SOA」框架的還有「Dubbo」,官方對它描述如下:
咦?「Dubbo3」的敘述是不是有些奇怪?是微服務框架,同時又是服務導向架構?
這問題我們晚點再說,我們先簡單總結一下,到底「SOA」是什麼?
它是面向「服務」的架構,就如同「服務導向架構(Service-Oriented Architecture,SOA) 簡介」一文中的概述:「『以客為尊』的核心概念,提供網路服務單位建構一個具彈性、可重複使用的整合性介面,加速達到網路服務提升的目標。」。
微服務架構(Microservices)
最後是「微服務架構」。
所以「SOA」又有什麼問題呢?「微服務架構」又是為了解決什麼呢?
事實上,「微服務架構」的提出並不是因為「SOA」有什麼問題,更精確的說,「微服務架構」與「SOA」的面相並不同,這之後再說,在此之前,我們先來談一下「微服務架構」的定義。
什麼是「微服務架構」呢?
在「IBM」的「微服務」一文中,它是這樣敘述的,如下:
如上所述,「微服務」是「應用」的架構,它的概念就是將一個整體的應用服務拆分成眾多「可獨立部署的小型服務」,如下圖:
但要注意的是,不論「微服務」如何分拆,在本質上,「微服務應用」仍舊是「一個應用」;而「微服務架構」就是一個專注於「解耦合」的「應用程式架構」。
接著,介紹一下關於「微服務」的基本組成要素,首先,在微服務架構中最主要的組成還是「服務群」本身。
而「服務群」基本是由眾多獨立且基於「單一職責」的小型服務所組成,每個小型服務都會是個完整且可以獨立部署的「系統」,在多數設計下,它們通常會擁有專屬的資料庫,如下:
當然「服務群」可能還會根據性質不同區分為「業務服務類」、「功能服務類」⋯等,但這邊姑且不提,皆以「服務」視之。
這邊有一點要特別注意,微服務是允許多個同功能的服務同時存在,這是為了「分流」與「備援」的需求;因為每個服務可能會因為硬體限制有無法負載的情況,因此「分流」是必須的配置,至於在微服務中是如何調控資源則與它的另一個組成要件:「註冊中心」相關,這我們稍後再說。
而「備援」更是相對重要的概念,在探討微服務架構時,我們通常都會提到「CAP」的概念;「CAP」是評估微服務特性的重要指標之一,其完整的概念我們稍後在敘述,在此我們先談其中的「A」,它代表的是微服務的「可用性(Availability)」,意即,即使微服務中的某個小型服務發生異常,但整個微服務仍必須正常運作;而「備援」的機制就是為了讓微服務滿足「可用性」的設計。
接著來介紹微服務間的「溝通」方式。
在維基百科的介紹中,它說:「服務間『不應該』有所溝通,即使是有,也應該以『異步機制』來避免其相依問題」,如下:
但現實是,服務與服務間幾乎不可能不相互溝通;甚至在部分的需求下,我們會需要以「同步機制」來達成服務間的溝通;對於這種相依性比較強的溝通方式,我們會稱為「直接溝通」。
在微服務的應用中,「直接溝通」最常使用的方式是符合「RESTful」設計風格的「HTTP」協議,即「RESTful API」。
備註:「HTTP」協議僅是一種常見的「網路通訊協議」,其與「同步」或是「非同步」並沒有直接關係。
既然有直接溝通,那必然有「間接溝通」,「間接溝通」通常是藉由所謂的中間項目作為溝通的橋樑,如所謂的「信息佇列」機制、「事件儲存、廣播中心」;其兩者皆為「異步機制」;但「事件儲存、廣播中心」通常是「一對多」、「訂閱者模式」的實踐,而前者則依實作不同而可能有所差異,但通常支援「一對一」,也支援「一對多」。
關於微服務的通訊方式可以參考「微服務架構中的通訊」。
至此,架構圖如下:
另外,由於小型服務間彼此是獨立的,所以彼此不能存在語言限制;這將與服務間的溝通方式有關,簡單的說,就是各個服務間傳遞的訊息要脫離語言框架。
關於「服務群」的特性還有一點就是:「服務間不能相互干擾」;也就是說假如倘若「服務 A」異常或無回應,那麼其它服務仍要正常運作,即使是其它的服務與異常的服務有「次序依賴」的關係,但它允許資料的不一致,例如,假設「獲取資料」的服務異常,那其相關功能可以顯示「暫時無法取得資料」,又或是顯示緩存服務中的數據資料;但不能因為其它服務的異常而跟著「異常」;這也是「CAP」定理中的「P」,即「分區容錯性(Partition tolerance)」。
在討論完「服務群」之後,接著是「資料儲存」的部分。
事實上,微服務的資料儲存架構的作法很多,但不論何種作法,都必須滿足資料的「可棄性」;也就是說,若某個服務內的資料丟失時,其不會影響整個微服務應用,常見的設計是除了每個小型服務中的獨立資料庫外,還會有共同資料庫,通常快取、緩存機制也都是必備的項目,如下:
再接著是「微服務」管理的部分,像是與「資源調控」的有關的「註冊中心」,或是與系統設定有關「文件配置中心」,如下:
再然後是匯整接口信息的「網關閘道」;與負載保護、導分流相關的「熔斷器」、「負載平衡」;以及「日誌管理」、「鏈路追蹤」⋯等,煉成,圖如下:
呃,不是,是這個:
最後,有些書籍也會把「CI/CD」的部分視為微服務架構的一部分,那也是沒問題的。
但要特別注意的是,「微服務」是一種風格的概念,並不是真正的實現,因此,上述的圖僅是一個舉例,並不代表所有微服務的架構設計都該如此,例如先前多提到的「Dubbo」框架,就與上述有些許差異,如下:
講個古,「Dubbo」是相當早期的框架,由「阿里巴巴」所研發,爾後開源給「Apache」,所以稱之「Apache Dubbo」。
在「Dubbo」被開發的當時,微服務的概念並不如現在完善,因此,若拿之與現今的流行的「微服務」框架相比,會顯得比較不足;而「Dubbo」的確是以「服務治理」為導向的架構,因此它是一種「SOA」。
那麼,是不是又回到稍早的問題了?
為什麼「Dubbo」可以是「微服務架構」,又是「SOA」呢?
同樣地,這我們稍後再說。
言歸正傳,我們剛才說「Dubbo」是一種「微服務架構」的實現,又說它略顯不足,那麼目前最夯的「微服務框架」是誰?
就撰文的當下,筆者認為非「Spring Cloud」莫屬,其圖如下:
事實上,「Spring Cloud」並非是種「單一技術或框架」,而是集合多項相關「開源項目」的技術棧,如「Eureka」、「Hystrix」、「Feign」⋯等,可以參考其「中文網站」,如下:
題外話,阿里巴巴基於「Spring Cloud」技術棧並整合「Dubbo」後,推出一套新的雲架構:「Spring Cloud Alibaba」;目前討論度相當高。
填個坑,為什麼「Dubbo」可以是「微服務架構」,又是「SOA」呢?
在此之前,我們必須先來談談「微服務」與「SOA」的差別?事實上,這兩者的差異更多是在於「關注面向」的不同。
就如同「IBM」在「SOA vs. Microservices: What’s the Difference?」一文中所講描述的:「微服務」和「SOA」兩者所關注的「範圍」不同。
首先,「SOA」是「提供服務」的架構,以「客」為尊,面向的是「服務對象」;而「微服務」的本質是「應用」,它是個「程式架構」,所以面向自然是「應用本身」。
實際上,「SOA」可能由涉及一個或多個應用,如下圖:
介紹到這邊,懂了吧?由於「微服務」和「SOA」的關注面向不同,所以它們並非完全地對立,在某些關注點上,「微服務」和「SOA」可以說是不同層級的概念;而這就是為什麼「Dubbo」可以同時是「SOA」又是「微服務架構」的原因了。
不過雖說如此,但兩者間的確有不少相似可比較之處,例如在「治理」方面,「SOA」是仰賴「治理中心」的統一整合與調度,具有「中心管理」的概念,例如「ESB」⋯等,而「微服務」雖然也有「註冊中心」,但其目的只是控管資源而已,亦無中心的概念。
在「溝通信息」的方面,「微服務」為了保持簡單,通常會使用輕量級消息傳遞協議,如「RESTful API」;而「SOA」則是較具選擇彈性。
在「顆粒度」方面,「SOA」需要考量「服務範圍」,而「微服務」則更講究「單一職責區分」;因此,在理想情況下,「微服務」應該是「完全地鬆耦合」。
「CAP」理論
最後填個坑,談一下在討論「微服務架構」時,一定會被提到的「CAP」定理;它又被稱作「布魯爾定理」,內容是說「一『分布式計算系統』不可能同時滿足『一致性(Consistency)』、『可用性(Availability)』與『分區容錯性(Partition tolerance)』。」。
可用性(Availability)
接著,我們一一解釋「C」、「A」、「P」分別說代表的意義。
首先是「A」,根據維基上的描述,其定義是:「每次請求都能獲取到非錯的響應」。
下圖為一「請求-響應」的示例:
此時,倘若節點發生異常,是不是就意味著這個服務就失效了?圖如下:
這樣的服務架構,就意味著缺乏可用性(Availability),因此,在散布式架構中,為了要讓服務具備可以性,我們通常會設置至少兩個以上的節點,如此一來,即使有一節點發生異常,那麼例外一個節點還是能夠正常提供服務,如下:
一致性(Consistency)
接著是「C」。
在微服務的架構中,每一節點都是獨立可部署的服務,意即它們都嘗試一個整體的服務,也擁有自己的數據副本,而一致性(Consistency),倘若兩節點是同樣服務,其數據副本的資料必須完全相同,如下:
分區容錯性(Partition tolerance)
最後是「P」,「分區容錯性」,它的意思是說,不能因為某些節點消失或故障時,就停止提供服務。
在分別介紹「C」、「A」、「P」個別所代表的意思後,我們來探討它對微服務的意義。
首先,我們必須知道一個觀念:「微服務架構」是一種「分布式系統」;而「CAP」開宗就明講:完全符合「C」、「A」、「P」三個特性的「分布式系統」是不存在的,所以當我們在設計微服務架構時,我們僅能依照需求去選擇「CA」、「CP」或「AP」的相關技術,如下圖:
圖片來源為「w3resource」。
事實上,在分布式系統中,零「P」,即「CA」,是幾乎不可能存在的,這是因為「分布式系統」的溝通通常是仰賴「網路通訊」的,而「網路傳輸」一詞也就意味著它不可能完全地穩定。
所以我們在評估技術時,通常只能在「AP」或「CP」中選擇,至於該如何考量,這就不是我們這篇文章的內容了,總之,我們要了解的是,「CAP」是一套用來評估的分布式系統的理論,其它關於「CAP」理論更多的資訊可以參考「CAP 理論」。
總結
回顧網站架構的發展過程,也許現行的架構與網站初期時的架構已經大相徑庭;但我們可發現,其過程是有跡的,是循序的,所有的架構被提出都是有其淵源,或許是為了解決問題,也或許是為了特定目的;但除了其各自的關注點外,其幾乎都圍繞著一個共同的核心:「容易管理、高維護性」。
的確,好的架構與好的套件的確能夠有效的幫助我們撰寫出一套相對容易維護與管理的程式。
但別忘了,撰寫程式碼的始終是程式設計人員本身,我們不僅是程式的「開發者」,更是程式的「維護者」;倘若自己不刻意維持良好的撰寫習慣,那不論是多嚴謹地架構,又或是如何地規範,那也是無功用。
倘若如此,那麼既使我們從「單體式架構」換到「微服務架構」,也不過就是從「屎」變成「許多屎」而已,如下:
圖片來源:「WWW 0x05: 若單體服務是屎,微服務就是許多屎」。
最後,於網站設計而言,架構本身並沒有高下之分,更沒有所謂的完美,有的只是合不合適,僅此而已;此外,既然沒有完美的架構存在,架構的發展必然不會停止。