這幾年隨著容器興起而,架構逐漸從單體式演進到低耦合的微服務設計。前文是微服務架構演進史的第一部分,介紹了何謂微服務與物件導向設計觀念,接下來第二部分將說明微服務架構的現況發展,以及軟體開發流程對架構和設計的影響。
網管人雜誌159期「走入軟體架構演進史 見證微服務發展今昔」一文說明了微服務架構之演進史(第一部分),包括微服務架構的過去、早期軟體開發到物件導向設計模式(Design Pattern)、服務導向運算(Service-oriented Computing)誕生、第二代服務導向出現、何謂微服務與物件導向設計觀念。
在第一部分後面,談到了五項物件導向設計原則(SOLID),包括單一職責原則(Single Responsibility Principle,SRP)、開閉原則(Open/Closed Principle,OCP)、里氏替換原則(Liskov Substitution Principle,LSP)、介面隔離原則(Interface Segregation Principle,ISP)、依賴反轉原則(Dependency Inversion Principle,DIP)。基於上述物件導向設計原則,Eric Evans提出了領域驅動設計(Domain Driven Design,DDD),業務邏輯拆分成核心領域(Core Domain)和領域邏輯(Domain Logic),並把設計重心放在限定上下文邊界(Bounded Context)的抽象模型上。微服務架構設計,便是依循上述的SOLID原則和DDD方法論。以下內容便以單一職責原則(SRP)來重構一個微服務架構。
微服務架構SRP實例
單一職責原則(Single Responsibility Principle,SRP)可以算是微服務架構最主要的設計理念,當然這又衍生出多個微服務為了維持交易的一致性產生出新的問題,進而催生了Saga Pattern(https://microservices.io/patterns/data/saga.html)。微服務架構中的每一個服務或模組都只專注做一件事,且做得盡善盡美,故以單一職責原則來作為微服務重構的實際案例。
首先,物流開發團隊發布了一項新的機車派送服務(稱為Deliveryhub),允許客戶透過App應用程式與物流士進行媒合與溝通,無須透過手機或簡訊,以便節省電話費和增加遞送效率。但是,系統永遠不會如同設計般那樣順利運行,由於種種原因,這服務導致了一些問題,讓開發人員在半夜被叫起來處理,因為這些經驗和教訓,因此決心為這項服務重新設計出更可靠的架構。
主要問題所在
總而言之,Deliveryhub依賴過多的其他服務才能正常運作,因此審視一下Deliveryhub為建立頻道而執行的一些任務步驟,如圖1所示:
1. 授權服務(Authentication Service):用來驗證手機使用者身分,涵蓋物流士與客戶等。
2. 取得客戶的個人資訊(Customer Service):這可透過呼叫客戶相關REST服務來取得。
3. 取得物流士的相關資訊(Driver Service):一樣是藉由呼叫物流士相關REST服務來取得。
4. 驗證客戶與物流士的運送配對是否正處於生效狀態(Active Booking Service):這會呼叫啟動運送單的儲存服務。
5. 建立頻道。
如果這些服務之中有任何一個錯誤,Deliveryhub也會跟著錯誤,如圖2所示。
同時依賴多項服務,進而導致其穩定性和妥善率永遠低於所使用到的每一個子服務,即便非常用心確保所有相依服務的正常運行時間可達99%可用率,但是這仍舊意味著所有的服務可同時運作之可能性只有96%。
可用率算法採交集方式,如下所示:
P|Deliveryhub = P|customer service active * P|driver service active * P|authentication service active * P|active booking storage active| = 0.99 * 0.99 * 0.99 * 0.99 ≌ 0.96
這代表著系統的停機時間將會增加四倍,是4%,而不是1%。
被錯怪的Deliveryhub
當一項服務做太多事情或相依太多子模組或服務,便遲早會出錯,在這種情況下,會讓Deliveryhub開發團隊和維運備感壓力,Deliveryhub成為關注的焦點和效能瓶頸點,Deliveryhub的工作是在客戶和物流士之間建立一個溝通頻道,但是它目前涵括了這些全部的額外事務,如身分驗證、授權和擷取個人資訊。
接下來,為了避免服務雪崩和堆積大量服務請求,逐步朝向移除服務相依性目標,以下便是架構上的重構:
認證
每次發送到Deliveryhub API的Request都附帶一個需要身分驗證的API Token,為了解決這個問題,如圖3所示,需要增加一個API Gateway,負責所有Request請求的身分驗證,並在API的HTTP Header中加上了已經通過身分驗證的使用者資訊。
現在每個發送到Deliveryhub的API Request請求都經過了認證,後端API請求會主動夾帶使用者資訊,無須再詢問,且通過API Gateway的請求皆有驗證身分及Token時效性,而非由Deliveryhub主動呼叫其他服務。
個人資訊擷取
為了建立溝通頻道,則需要為每個使用者建立一個稱為「Chat Token」的資料,這會分別儲存在客戶服務的客戶資訊內,以及物流士服務的物流士資訊之中,如圖4所示。
由於Deliveryhub是使用此Token的唯一服務,因此將這些Token移到其中,並且從客戶(Customer Service)和物流士服務(Driver Service)之中移除,如圖5所示。
現在Deliveryhub在其本身的資料庫中具備了所需的必要資訊,與之前調整前的整個服務相比,這成為更可靠更穩定的系統服務。因此,如果目前的服務已經是唯一會使用相關資料的服務,那這些資料就應該保留在這服務本身,也是唯一會存取這些資料的服務。
啟動運送單儲存
當使用者按下App的聯繫按鈕,便會呼叫Deliveryhub的Create API即時建立溝通頻道,方便客戶與物流士聯絡,而這樣即時建立的功能需要驗證運送配對服務(Active Booking Service)是否存在有效配對紀錄,畢竟當雙方彼此完全沒有運送配對關係時,建立溝通頻道完全沒有意義,只是浪費系統資源。
為了解決這樣問題,便將頻道建立工作搬移到非同步架構上(圖6),以便取代目前立即的溝通頻道建立方式,因此改用數據管道,當有運送配對建立時,就可發送事件,亦可讓服務與服務之間去除耦合性,更易於抽換,此為生產者消費者模式(Producer-Consumer Pattern),現在Deliveryhub服務是由Worker和Server兩個元件所組成。
1. Worker元件每次被Server呼叫時,皆會產生配對事件。接著,它在配對過程中在客戶和物流士之間建立一條溝通頻道,並且將頻道資訊存放到Redis的暫存記憶體內。
2. Server元件就像之前一樣,提供頻道建立請求的服務,只是,這一次會去檢查Redis暫存中是否已經存放了頻道資訊和訂單號碼,除了節省系統資源外,還可加速回應使用者,縮短系統回應時間。
因此,取代了舊有的即時頻道建立方法,改以非同步建立,並且快取儲存頻道資訊,若使用自架的內部數據管道來消費這些配對事件,便無須驗證傳入事件的真偽。
「關鍵重構方法:主動告知,不用詢問」。當來自數據管道的事件告訴Deliveryhub,那配對是真實存在的,便意味著它可以建立溝通頻道,而不用再去呼叫其他服務來驗證其訂單的真實性。
最後成果
現在Deliveryhub只負責它真正想做的唯一事情:「建立溝通頻道」。
由於移除了大多數外部系統或服務的依賴關係,終於不再需要擔心一個系統故障導致Deliveryhub出現故障,由於Deliveryhub不再使用本身服務來驗證溝通頻道建立,再加上非同步機制,因此外部其他服務的負載也因此減輕了。
遷移到非同步架構,還可讓系統回應時間大幅度縮短,大約從X00毫秒(ms)到X0毫秒,部分原因也是為每個訂單預先建立好快取,並暫存了溝通管道資訊。
上述的實際案例便符合單一職責原則,最後可養成自我審視習慣,詢問自己所負責的服務是否可以套用此原則,並減低其他服務的相依性。
微服務架構的現況
這幾年微服務架構逐漸成為撰寫應用程式的新典範,藉由組合小型的服務,每個服務運行自己的流程並透過輕量級通訊方法進行溝通,此種方式建立在服務導向架構(SOA)的概念之上,這些概念來自於在應用程式等級上的跨邊界工作流程(Business Process Management,BPM),引進到應用系統架構之中,像是將原本肥大的服務導向架構重構改寫成輕量單元,正因為受到BPM流程管理的啟發,「單體式架構拆解成微服務,必須從業務面和流程面來著手」。
此「微服務」名詞術語,首先在2011年的軟體架構研討會上出現,主要用來描述與會者在軟體架構模式上共通理念的一種方式,在那之前,此架構方法也以不同的名稱而廣為人知,例如Netflix使用了Fine Granined SOA(細粒度SOA)的名稱來形容一個非常相似的架構。
微服務是目前軟體架構的一項新趨勢,它強調高度可維護性和可擴展軟體的設計和開發,微服務透過在功能面上將大型複雜系統分解成多組個別獨立的服務來管理日益增加的複雜度,藉此讓服務在開發和部署過程中完全獨立,微服務因為其模組化來達成高內聚和低耦合性,此方法在可維護性、可擴展性等方面皆有出色的表現和益處,但它仍舊存在一系列的問題,這些問題從分散式系統和SOA繼承而來,但微服務架構仍然顯露出獨特的優勢,而與SOA本身截然不同的獨特之處如下:
‧尺寸(Size):尺寸相對比一般的服務還小,支撐這樣的系統架構設計是高度依賴於生產它的團隊組織,當中微服務架構設計之觀念及常用的方法是,若服務太大,則應拆分為兩個或更多的服務,進而維持細粒度並持續專注於只提供單一業務面功能,這可讓服務在可維護性和可擴展性方面帶來益處。
‧上下文邊界(Bounded Context):相關功能組合成單一業務面功能,然後實作成為API服務。
‧獨立性(Independency):微服務架構中的每個服務在操作上皆獨立於其他服務,服務之間唯一的溝通方式就是透過其所已發布的API介面。
除此之外,微服務在系統面上的重要特點是:
‧靈活性:系統能夠跟上不斷變化的整體業務需求,以及改變所需的彈性,以便能夠支持團隊組織在市場上維持競爭力。
‧模組化:系統是由相互隔離的元件所組成,且當中每個元件都是不可或缺的一環,有助於整個系統運行,而不是由單一個元件來提供全部功能。
‧演化:根據上述特點,系統便可保持高度可維護性,同時可不斷發展並添加新功能。
近來,微服務架構受到開發者的歡迎及擁抱,且目前仍處於剛起步階段,因此在微服務實務面及實作上仍然缺乏共識。Martin Fowler(馬丁‧福勒,ThorghWorks首席科學家)和James Lewis定義了微服務的主要特點(https://martinfowler.com/articles/microservices.html),成為軟體架構師學習微服務的一個參考文獻起點,且ThorghWorks軟體顧問公司定期會發布技術雷達(https://www.thoughtworks.com/radar),評估各種新技術和新軟體的可用報告,強烈推薦CTO和架構師們作為技術選型的參考資料,Sam Newman更以Martin Fowler的文章為基礎,撰寫了上述文章中架構上的特定問題或用途之解答和最佳實踐,其書名為「Building Microservices: Designing Fine-Grained Systems」(中文版:建構微服務-設計細微化的系統)。
除此之外,還有許多論文,描述了使用微服務架構設計和實作系統的相關細節,例如「Microservice-based Architecture for the NRDC」的作者描述使用微服務架構為內華達研究資料中心(NRDC)建構了新軟體系統的相關開發細節,還有Mazedur Rahman和Jerry Gao應用了行為驅動開發(Behavior-Driven Development,BDD)在微服務架構之中,以便減少開發人員在維護的工作負擔,並且建議採用驗收測試。
組織團隊
其實早在1968年,Melvin Conway便提出組織對產品的影響:康威定律(Conway's Law),「Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.」,(公司開發的產品和服務其實都是反映著公司自身組織架構、溝通與工作方式)。更具體地說,企業組織的溝通結構限制了系統的設計,導致最終的設計是複製出組織的溝通模式,像是軟體開發部門與使用者們溝通不良,必定設計出不符合使用者需要的系統。
微服務方法是組織那些圍繞於服務四周的跨功能團隊,而服務又圍繞著業務邏輯和能力,此方法也被稱為「You build, you run it」(你建立,你執行)的原則,最先由亞馬遜CTO長Werner Vogels所導入,提出「Two pizza teams」的團隊人數限制,根據這樣開發方法,團隊須負責整個開發到維運的軟體生命週期,詳細的亞馬遜開發方法,可參閱「How Is Software Developed At Amazon?」(http://highscalability.com/blog/2019/3/4/how-is-software-developed-at-amazon.html)。
全面自動化
每個微服務或許表示單一業務可乘載能力,其可完全獨立地依照自身的時程表來派送交付和更新,當發現程式錯誤或須增添改進功能時,並不會影響到其他服務和它們的上線發布計畫(當然,需要保留向下相容性並且服務介面規格維持不變)。但是,要真正發揮獨立自主部署的力量,必須利用非常高效率的整合和交付機制。
話雖如此,但微服務是在持續交付之後所發展出來的第一種架構,所以基本上微服務架構就意味著具備持續交付和持續整合開發流程,讓交付流水線的每個階段都自動化,藉由使用自動化的持續交付流水線和現代化的容器工具,可以在幾秒鐘內將需上線更版的服務部署到營運環境中,這自動化流程已被證明在快速變化的業務環境中是非常有幫助的。
以編排取代協作
如前所述,微服務可以協同合作以便提供更複雜和更細緻的功能。而達成這樣合作模式有協作(Orchestration)和編排(Choreography)兩種方式。協作方法需要一個指揮家:一項中心式服務,它將對其他服務發送請求,並透過接收回應來監督其過程。另一方面,編排則設想去中心化並使用事件和發布╱訂閱機制(Pub/Sub)來建立協同合作,這兩種概念對於微服務來說並不陌生,是繼承源自SOA世界,其中像是WS-BPEL和WS-CDL等語言分別成為了協作和編排的主要參考(在這兩大技術社群的擁抱者之間有激烈的討論)。在微服務出現之前,特別是在SOA開始炒作之前,協作方式由於使用簡單和輕易地管理複雜環境,通常是更受歡迎且廣泛被採用,然而,它顯然導致服務耦合性和責任分配不均且不清楚,因此會有一些服務比其他服務更集中化的現象。
微服務的權力下放文化和高度獨立性,代表了使用編排作為實踐協作的手段是較自然貼切的應用情境。最近,這樣方法再次引發對微服務在更大量服務擴增問題上的熱烈討論和興趣,目前朝向Service Mesh方向發展。而微服務協同運作機制若沒規劃好,很可能就如圖7所示的情況一樣。
在品質與管理面的衝擊
為了更深入掌握微服務架構,需要了解該架構對特定軟體品質特性的影響。
可用性(Availability)
可用性是微服務中的主要問題,因為它會直接影響到系統的正常運作。有鑑於服務之獨立性,可以根據組成系統之各個服務的可用性來評估完整系統的可用性(如前面提到的可用率算法),即使是單一服務無法回應請求,整個系統也可能會受到損害並產生無法使用的後果。如果採用微服務方式來實作,組件則越容易出錯,系統就會越頻繁地發生故障。人們常會爭辯說,小尺寸元件應可形成較低故障發生率,然而,研究卻發現,小尺寸軟體元件通常具有非常高的密集故障率。
另一項研究發現,隨著系統規模的增加,部件元件的故障頻率也會隨之增加。由於微服務架構的慣用方式顯露出,隨著系統變得越來越大,應該去防範微服務變得過於複雜,透過將它們重新組合成兩個或更多不同的服務,以便防止微服務過於龐大,因此維持服務的最佳大小,理論上可以提高可用性。
另一方面,產生越來越多的服務亦容易使得系統在整合階段上出錯,因為要讓數十個服務立即可以使用涉及了大量複雜問題,這將會導致可用性降低。
可靠性(Reliability)
有鑑於微服務架構的分散式特性,應特別注意服務之間的訊息傳遞機制之可靠性以及服務本身的可靠性。採用小而簡單的元件來建構系統也是導入微服務架構的通則之一,為了獲得更高的可靠性,必須找到一種方法來管理大型系統的複雜性:「以清楚的介面定義,來建置簡單的元件是實現此一目標的唯一方法。」而微服務之可靠性的最大威脅是在整合範疇,因此在探討微服務之可靠性時,就必須討論到整合運作機制。而誤把網路視為整合機制中可靠的一環,是一種錯誤的觀念或假設,著名的「分散式計算之謬論(Fallacies of distributed computing)」,八項錯誤的假設,當中第一項就是「The network is reliable」。因此,在這方面,微服務可靠性遠不如使用記憶體中調用呼叫的應用程式。
值得注意的是,此項關鍵問題不僅僅只是出現在微服務架構,而是在任何分散式系統皆可能發生。在討論訊息傳遞可靠性時,請記住以限制條件來約束微服務的整合機制也很有幫助。
更具體來說,微服務採用整合運作機制是以非常直接了當的方式:移除與訊息傳遞無關的所有功能,因每個單一服務已經先經過驗證其功能,僅須關注於可靠的訊息傳遞。
可維護性(Maintainability)
從本質上來看,微服務架構是鬆散耦合的,這意味著服務和服務本身之間只存在少量的獨立鏈接。透過最小化方式修改其服務,修復錯誤或添加新功能的成本,這對於系統的可維護性有非常大的幫助。
儘管耗費一切努力讓系統盡可能易於維護,但總是會有含糊不清和違反常理的程式碼會影響可維護性。因此,就微服務的另一個構面來看,能提高可維護性便是前面所提到的「You build, you run it」(你建立,你執行)的原則,此原則可更好地理解其服務的業務能力和角色定位。
效能(Performance)
導致微服務架構效能降低的最大因素是網路間的訊息傳輸,網路延遲時間遠大於記憶體延遲,這意味著記憶體內(In-memory)呼叫遠比透過網路發送消息要快很多,因此若以網路傳遞和使用記憶體內呼叫機制的應用程式相比,效能就會差很多,這項因素也間接地導致了微服務規模的限制,在一般常見的架構中並沒有與規模大小相關的限制,其記憶體內程式呼叫占所有次數的比例是高於微服務架構,形成透過網路呼叫程式的比例較少,因此效能降低的確切數量仍取決於系統的相互連接性。所以,具有良好上下文邊界(Bounded Context,https://martinfowler.com/bliki/BoundedContext.html)的系統將因更鬆散的耦合性和更少的訊息發送量,面臨較慢的系統退化或系統淘汰。
安全性(Security)
在所有分散式系統中,安全性皆為關鍵議題。以常理來看,微服務亦會遭受與SOA相同的安全漏洞,由於微服務使用REST機制和JSON或XML作為主要資料交換的格式,因此須特別注意傳輸過程中的資料安全性,這表示額外的加密功能會讓系統增加額外的效能損耗。
微服務增加了服務可重用性,因此自然而然地假設所有系統皆安全無慮,包括第三方服務,所以另一項挑戰就是提供認證授權機制給第三方服務,並確保能安全地儲存其所發送來的資料,以利後續判斷責任歸屬。概括而論,微服務的安全性是以相當被動的方式來處理,因為必須考慮實作其他安全機制以便提供上述之外的安全功能。
可測試性(Testability)
由於微服務架構中的所有元件皆是各自獨立,因此每個元件都可以單獨測試,與單體式架構相比,明顯提高了元件的可測試性,微服務架構還允許依變動的大小來調整測試的範圍,這意味著透過微服務,就可隔離已變更的系統部分和受到變更影響的部分,可獨立於系統的其他部分對其進行單獨測試。
但另一方面,整成測試就可能變得非常棘手,尤其是當欲測試的系統非常大且元件之間的相互連接太多時,雖然可以單獨只測試每一個服務,但很多異常是只有在許多服務一起協同運作時才會發生。
結語
隨著軟體系統的發展,軟體架構也在不斷發展,軟體工程實踐方法越來越多,以便滿足每一個時代面臨的新挑戰。目前軟體架構是主流軟體工程中比較少探討的隱藏部分,需要更加重視及經驗判斷來決策,而不是單一制式定義,軟體演化的下一階段確定會變動更大,即時人工智慧系統、雲端平台部署以及連接IoT端點的串流系統,這些將讓軟體架構師面對更大的難題和更多的解決方案。下一篇將討論微服務架構的未來發展和挑戰,敬請期待下一期的微服務架構文章。
<本文作者:鄭淳尹,Docker/Moby.Taipei社群共同發起人,現為momo購物網資深工程師,曾任臺北榮民總醫院資訊工程師、玉山銀行資訊處專員、宏碁eDC維運工程師,系統維護及開發設計超過15年。開源技術愛好者,陸續在COSCUP開源人年會、Container Summit研討會、台灣微軟開發者大會、群益期貨和永豐金證券等分享資訊技術,並在多間大學資工系擔任Docker容器技術講師。現任微軟MVP,並翻譯審閱多本容器技術書籍。>