微服務架構設計中的設計模式、原則及最佳實踐

作者 | Mehmet Özkaya

譯者 | 平川

策劃 | 閆園園

本文既有理論知識,又有實用信息:我們將學習每一種具體的模式,爲什麼以及應該在什麼地方使用;然後,我們將看下應用了這些模式的參考架構;接下來,我們將綜合運用新學到的模式設計我們的架構;最後,我們將確定選用什麼技術實現架構。

本文將介紹微服務架構設計中的設計模式、原則及最佳實踐。我們將使用適當的架構設計模式和技術。

通過本文,你將瞭解到如何利用微服務分佈式架構設計一個高可用、高可擴展、低延遲且對網絡故障有彈性的系統,以處理數以百萬計的請求。

事件驅動的架構

本文將教給你如何從單體架構一步步演進到事件驅動的微服務架構。

我們將從基本的軟件架構設計入手,設計一個可以處理少量請求的單體架構的電子商務應用。

架構設計之旅

之後,我們將介紹該架構如何一步步演進:

1 本文內容組織

本文既有理論知識,又有實用信息:

因此,我們會對架構進行迭代設計,從單體架構逐步演進爲事件驅動的微服務架構。

2 架構演進

我們將根據以下問題來演進架構:

因此,我們是從以下幾個方面來改進架構:

可擴展性和可靠性可以衡量應用程序能夠爲終端用戶提供何種程度的服務。如果我們的電子商務應用可以爲數百萬用戶提供服務而不出現可以覺察的停機,那麼我們就可以說這個系統是高度可擴展和可靠的。可擴展性和可用性可能是設計良好的架構需要考慮的主要因素。

可擴展性 = 電子商務應用程序應能爲數百萬用戶提供服務

可用性 = 電子商務應用應該 7*24 小時可用

可維護性 = 電子商務應用應該可以發展數年之久

效率 = 電子商務應用的響應延遲在可接受的範圍內,如小於 2 秒,即低延遲

每秒請求數和可接受的延遲

現在讓我們看下可接受的延遲。如果我們的應用程序用戶越來越多,我們如何讓應用程序的延遲在可接受的範圍內?請看下錶:

從表中可以看出,我們的電子商務應用是一個小型應用,開始只有 2K 併發用戶,每秒 500 個請求。我們將根據預期的體量來設計該電子商務應用的架構。

之後,隨着業務不斷增長,它將需要更多的資源來適應更大的請求數,你將看到我們如何根據這些數值來演進我們的架構。

單體架構

幾十年的軟件開發演變出了許多方法和模式,它們各有各的優勢,也都面臨着各自的挑戰。

因此,我們將從理解現有的方法入手來設計電子商務應用的架構,並逐步演進轉移到雲上。

爲了理解雲原生微服務,我們需要理解什麼是單體應用程序,以及我們如何從單體架構遷移到微服務架構。

對於遺留應用,可以說大部分都是以單體架構爲主實現的。

如果一個項目的所有功能都在一個代碼庫中,那麼該應用就是單體應用。在單體模式中,用戶界面、業務代碼和數據訪問的所有東西都在同一個代碼庫裏。

所有應用關注點都包含在一個大的部署中。即使是單體應用也可以設計出不同的層次,如表現層、業務層和數據層,然後將該代碼庫部署爲單個 jar/war 文件。

單體方法也有不少優點,我們將在即將推出的視頻中討論它們。在這裏,我將介紹一些主要的優缺點。

由於只有一個代碼庫,所以很容易拉取並參與其中。而且,因爲在同一個項目中,所以不同模塊間的業務交互很容易調試。

遺憾的是,單體架構有許多許多缺點,如:

如你所見,我們瞭解單體架構。

什麼時候採用單體架構

雖然單體架構有很多缺點,但如果你正在構建一個小型應用程序,那麼單體架構仍然是你可以在項目中採用的最佳架構之一。因爲,在許多方面,單體應用程序都比較簡單。

它們很容易:

與需要有經驗的開發人員來識別和開發服務的微服務架構相比,它的開發相對簡單。它更容易部署,因爲只需要部署一個 jar/war 文件。

3 單體架構設計

在這一節中,我們將使用單體架構一步一步地設計我們的電子商務應用程序。我們將根據需求逐步對架構設計進行迭代。

我們應該總是從編寫 FR(功能需求)和 NFR(非功能需求)開始。

功能需求

非功能需求

此外,最好在架構圖中加上規則,以免忘記。

原則

我們在設計架構時會考慮這些規則。

如你所見,我們使用單體架構設計了電子商務應用。

我們添加了一個大的 E-Commerce 框,它是電子商務應用程序的組成部分,其中包括商店用戶界面、分類服務、SC 服務、折扣服務、訂單服務。如你所見,這個傳統 Web 應用程序的所有模塊是容器中的一個工件。

這個單體應用有一個龐大的代碼庫,其中包括所有模塊。如果要在這個應用程序中增加新模塊,就必須對現有的代碼進行修改,然後將代碼修改後的工件部署到 Tomcat 服務器上。簡單起見,我們遵循 KISS 原則。

我們將根據需求重構我們的設計,並一步步進行迭代。

單體架構的可擴展性

從圖中可以看出,我們增加了 2 臺應用服務器,對單體架構做了橫向擴展,並在單體應用的客戶端和電子商務應用之間加了一個負載均衡器。

在單體架構中,爲了實現擴展,我們需要增加 E-Commerce 應用服務器,並在應用程序之前放一個負載均衡器。

本質上,負載平衡器將接受請求並使用一致性哈希算法將請求發送到電子商務應用服務器,保證服務器的負載都一樣。

適配技術棧

現在我們看下技術選項——適配技術棧。

從圖中可以看出,我們已經爲電子商務單體應用選出了潛在的選項。負載均衡,NGINX 是很好的選擇,還有 Java——Oracle 爲這類應用提供了標準實現。

4 微服務架構

微服務是小型的業務服務,它們可以自主 / 獨立部署,協同工作。

以下內容來自 Martin Fowlers 介紹微服務的文章:

微服務架構風格是一種將單個應用開發成一套小型服務的方法,每個服務都在自己的進程中運行,並通過輕量級的機制進行通信,通常是 HTTP 或 gRPC API。

因此,我們可以說,微服務架構是一種雲原生的架構方法,利用這種方法設計出的應用程序由許多松耦合的、獨立部署的小型組件組成。

微服務

——有自己的技術棧,包括數據庫和數據管理模型;

——通過 REST API、事件流和消息代理等相互通信;

——按業務能力來組織,劃分服務的界限通常被稱爲有界上下文。

在接下來的章節中,我們還將看到,如何利用有界上下文解耦微服務。

微服務的特性

微服務的特點是小、獨立和松耦合。一個小型開發團隊就可以編寫和維護一個服務。每個服務都有一個獨立的代碼庫,可以由一個小型開發團隊來管理。

服務可以獨立部署。團隊可以更新一個現有的服務,而不需要重新構建和部署整個應用程序。

服務負責持久化它們自己的數據或外部狀態。這點與傳統模式不同,在傳統模式中,有一個單獨的數據層處理數據持久性。

微服務架構的好處

敏捷性

微服務最重要的一個特點是小,可以獨立部署。

目標明確的小型團隊

微服務應該足夠小,以至於一個單功能團隊就可以構建、測試和部署它。

可擴展性

微服務可以獨立擴展,你可以單獨擴展某個子服務,而無需擴展整個應用程序。

微服務架構面臨的挑戰

複雜性

微服務應用程序有很多服務組成,這些服務需要協同工作來創造價值。由於服務很多,與單體應用相比,這意味着更多的移動部件。

網絡問題和延遲

由於微服務很小,而且服務之間需要通信,所以我們要管理網絡問題。

數據一致性

微服務有自己的數據持久化。因此,數據一致性會成爲一項挑戰。

5 微服務架構設計

在這一節中,我們將一步步地設計微服務架構,並根據需求,逐步迭代架構設計。

在設計微服務架構時,我們遵循了 "服務獨享數據庫模式"。微服務是單體應用模塊分解而成的獨立服務。

如此一來,現在這些數據庫就可以是混合持久化。也就是說,根據每個微服務的存儲需求不同,Product 微服務可以使用 NoSQL 文檔數據庫,SC 微服務可以使用 NoSQL 鍵值對數據庫,Order 微服務可以使用關係型數據庫。

架構演進

讓我們看下這個微服務架構圖,並思考一下這個架構缺少了什麼?這個架構的痛點是什麼?我們怎麼改進這個架構,才能提供更高的可擴展性、可用性,並且支撐更多的併發請求?

我們看到,UI 和微服務是直接通信的,這看上去很難管理。我們現在應該重點關注下微服務通信。

6 微服務通信

當遷移到基於微服務的應用程序時,最大的挑戰之一是通信機制的變化。因爲微服務是分佈式的,微服務之間的通信是通過網絡層面的服務間通信完成的。每個微服務都有自己的實例和進程。

因此,服務必須使用服務間通信協議,如 HTTP、gRPC 或消息代理協議 AMQP 進行交互。

由於微服務擁有複雜的結構,服務都是獨立開發和部署,所以我們在考慮通信類型時應該謹慎,並在設計階段做妥善處理。

微服務通信設計模式——API 網關模式

如果你想基於微服務設計和構建具有多個客戶端應用程序的複雜的大型應用程序,則建議使用 API 網關模式。

該模式提供了一個反向代理,將請求重定向或路由到內部微服務端點。API 網關爲客戶端應用程序提供一個單一的端點,它會在內部將請求映射到內部微服務。我們應該在客戶端和內部微服務之間使用 API 網關。

API 網關可以處理像授權這樣的橫切關注點。因此,授權可以在集中式的 API 網關中處理,併發送給內部微服務,而不是在每個微服務中編寫相關代碼。同時,API 網關控制到內部微服務的路由,並能夠將幾個微服務的請求彙總到一個響應中。

總之,API 網關位於客戶端應用程序和內部微服務之間。作爲一個反向代理,它將請求從客戶端路由到後端服務。它還提供橫切關注點,如身份認證、SSL 終止和緩存。

微服務通信設計模式——API 網關設計

我們將對電子商務應用程序的架構進行迭代,增加 API 網關模式。

從上圖可以看出,客戶端請求由單個入口點收集並路由到內部微服務。

它將處理客戶端請求,並提供內部微服務路由。它還可以聚合多個內部微服務來響應一個客戶端請求,並提供橫切關注點,如身份認證和授權、速率限制和節流等等。

架構演進

我們將繼續演進我們的架構,但請看一下當前的設計,考慮下我們可以如何改進?

這裏,有多個客戶應用程序連接到單個 API 網關。我們應該小心這種情況,因爲如果我們在這裏只放置一個 API 網關,這意味着這裏存在單點故障的風險。如果客戶端應用程序增加,或 API 網關中業務邏輯的複雜性增加,這將是一種反模式。

因此,我們應該用 BFF-backends-for-frontends 模式解決這個問題。

微服務通信設計模式——Backends for Frontends(BFF)模式

基本上,Backends for Frontends 模式是針對特定的前端應用程序設置單獨的 API 網關。我們有多個供前端應用消費的後端服務,我們在它們之間設置了 API 網關,用於處理路由和聚合操作。

不過,這產生了單一故障點。爲了解決這個問題,BFF 提供了多個 API 網關,並根據客戶端應用程序的邊界進行分組,然後劃分到不同的 API 網關。

單個複雜的 API 網關存在風險,並且會成爲架構的瓶頸。通常,比較大的系統會按照客戶端類型(如移動、Web 和桌面功能)暴露多個 API 網關。當你不想爲多個界面定製單一的後端時,BFF 模式很有用。

所以我們應該根據用戶界面的不同創建多個 API 網關。這些 API 網關可以與前端環境實現最佳匹配,而不用擔心影響其他前端應用程序。

Backend for Frontends 模式爲實現多網關指明瞭方向。

微服務通信設計模式——Backends for Frontends(BFF)模式設計

我們將根據 Backends for Frontends(BFF)模式迭代我們的電子商務應用架構,增加 API 網關。

如你所見,我們在應用程序中加入了多個 API 網關。

微服務通信設計模式——後端內部微服務的服務間通信

我們已經在微服務架構中創建了 API 網關,而且已經說過,來自客戶端的所有同步請求都通過 API 網關進入內部微服務。

但是,如果客戶端請求需要訪問多個內部微服務怎麼辦?我們如何處理內部微服務之間的通信?

在設計微服務應用程序時,我們應該注意後端內部微服務之間的通信方式。最好的做法是儘可能地減少服務間通信。

然而,在某些情況下,由於客戶的要求或所請求的操作需要訪問幾個內部服務,我們無法減少內部通信。

例如,對照上圖考慮這樣一種情況:用戶想要結賬並創建一個訂單。

我們該如何滿足這個請求?

這些調用把微服務耦合在了一起,在我們的例子裏,微服務 Product 和 Pricing 就會相互依賴並耦合。如果其中一個微服務發生故障,它就不能向客戶端返回數據,所以它沒有任何容錯性。如果微服務之間的依賴性和耦合性增加,就會產生很多問題,並縮小微服務架構的優勢。

如果客戶要對購物車進行結賬,這將觸發一系列的操作。因此,如果我們試圖用請求 / 響應這種同步消息模式來執行這個下單用例,那就會像上面這個圖一樣。

可以看到,一個客戶端的 http 請求會觸發 6 個同步 http 請求。所以顯然會增加延遲並對系統的性能、可擴展性和可用性產生負面影響。

如果我們有這樣的用例,如果第 5 步或第 6 步失敗了,或者中間的某些服務中斷了怎麼辦?即使沒有中斷,某些服務也可能非常繁忙,無法及時響應,造成不可接受的高延遲。

那麼,這類需求的解決方案是什麼?

我們可以通過兩種方法來解決這種問題:

  1. 利用消息代理系統將微服務之間的通信變成異步方式,我們將在下一節中看一下怎麼做。

  2. 使用服務聚合模式將一些查詢操作聚合到一個 API 網關。

微服務通信設計模式——服務聚合模式

爲了儘量減少服務之間的通信,我們可以使用服務聚合模式。基本上,服務聚合設計模式是接收來自客戶端或 API 網關的請求,然後分配給內部多個後端微服務,再將結果合併,並在一個響應結構中發給請求發起人。

通過實現服務聚合模式,可以減少客戶端和微服務之間的通信量和通信開銷。

微服務通信設計模式——服務聚合模式設計  

在這一節中,我們將通過添加服務聚合模式 / 服務註冊模式,來迭代我們的電子商務應用架構。

如上圖所示,我們在電子商務應用的架構中應用了服務聚合模式 / 服務註冊模式。

7 基於異步消息的微服務通信

如果通信只是在少數幾個微服務之間進行,那麼同步通信就很好。但當涉及到多個微服務相互調用,並且要等待一些長時間的操作完成時,我們應該使用異步通信。

否則,微服務的相互依賴和耦合會導致瓶頸和嚴重的架構問題。

如果你有多個微服務需要彼此交互,而且,你希望這種交互沒有任何依賴性或是松耦合的,那麼我們就應該在微服務架構中使用基於異步消息的通信。

因爲基於異步消息的通信有賴於事件,所以我們稱這種通信爲事件驅動的通信。

發佈 - 訂閱設計模式

發佈 - 訂閱是一種消息傳遞模式,它的消息發送者被稱爲發佈者,而特定的接收者被稱爲訂閱者。

因此,發佈者不是直接將消息發送給訂閱者,而是將發佈的消息進行歸類,並送入消息代理系統,但並不知道有哪些訂閱者。同樣地,訂閱者只接收感興趣的消息,而不知道哪些發佈者在發佈消息。

微服務異步通信設計模式——發佈 / 訂閱消息代理設計

在這一節中,我們將添加發布 / 訂閱消息代理,提供微服務異步通信設計,完成電子商務應用的架構迭代。

可以看到,我們應用了發佈 / 訂閱消息代理。

如果要適配技術棧,那麼我們首先會考慮選用什麼發佈 / 訂閱消息代理。下面這兩個都是不錯的選項:

  1. Kafka

  2. RabbitMQ

8 微服務數據管理

在單體架構中,查詢不同的實體非常方便,因爲是由單個數據庫來管理數據,這會很簡單。多表關聯查詢也很簡單。對數據的任何修改都會一起更新,或是一起回滾。關係型數據庫具有嚴格的一致性,有 ACID 事務保證,所以管理和查詢數據都很容易。

但在微服務架構中,當我們使用 “混合持久化” 時,這意味着每個微服務都有不同的數據庫,包括關係型數據庫和 NoSQL 數據庫,我們應該制定一個策略,在進行用戶交互時管理好這些數據。

因此,這意味着我們在處理微服務之間的數據交互時有幾種模式和做法,我們將在本節中學習這些模式和原則。

微服務是獨立的,只執行特定的功能要求。在我們的電子商務應用中,我們有產品、購物車、折扣、訂單等微服務,它們需要彼此交互來滿足客戶的要求。這意味着它們需要頻繁地交互。而這些交互大多是查詢每個服務的數據以進行聚合或執行邏輯。

CQRS 設計模式

CQRS 是跨微服務查詢的重要模式之一。我們可以使用 CQRS 設計模式,以避免複雜的查詢,擺脫低效的連接。CQRS 是命令和查詢責任隔離的意思。本質上,這種模式實現了數據庫讀取和更新操作的分離。

爲了隔離命令和查詢,最好的做法是用 2 個數據庫物理地分離讀和寫數據庫。此時,如果我們的應用程序是讀密集型的,也就是讀比寫多,我們就可以通過定義自定義數據模式來優化查詢。

物化視圖模式是實現讀數據庫的一個很好的例子。因爲通過這種方式,我們可以用預定義的細粒度數據來避免複雜的連接和映射,進行查詢操作。

通過這種隔離,對於讀數據庫和寫數據庫,我們甚至可以使用不同的數據庫類型,如使用 NoSQL 文檔數據庫進行讀取,使用關係數據庫進行 CRUD 操作。

事件源模式

我們已經學習了 CQRS 模式,該模式主要是與事件源模式一起使用。當搭配使用 CQRS 與事件源模式時,主要的理念是將事件存儲到寫數據庫中,這將是作爲真相來源的事件數據庫。

之後,CQRS 設計模式的讀數據庫通過非規範化表提供數據的物化視圖。當然,這個物化視圖的讀數據庫消費了來自寫數據庫的事件,並將它們轉換爲非規範化的視圖。

事件源模式改變了數據保存至數據庫的操作。它不是將數據的最新狀態保存到數據庫,而是將所有事件按數據事件發生的順序保存到數據庫。這個事件數據庫稱爲事件存儲。

它不更新數據記錄的狀態,而是將每個修改追加到一個事件的順序表中。因此,事件存儲成爲數據的真實來源。之後,這些事件存儲通過物化視圖轉換爲讀數據庫。這種轉換操作可以通過發佈 / 訂閱模式來處理,實現方式是用消息代理系統發佈事件。

架構設計——CQRS、事件源、最終一致性、物化視圖

我們將在電子商務應用的架構中應用 CQRS、事件源、最終一致性、物化視圖。

因此,當用戶創建或更新訂單時,我將使用關係型寫數據庫,而當用戶查詢訂單或訂單歷史時,我將使用 NoSQL 讀數據庫,並在通過發佈 / 訂閱模式使用消息代理系統同步兩個數據庫時使它們保持一致。

現在我們可以考慮這些數據庫的技術棧了,我打算用 SQL Server 作爲關係型寫數據庫,用 Cassandra 作爲 NoSQL 讀數據庫。當然,我們將使用 Kafka 的發佈 / 訂閱主題交換來同步這兩個數據庫。可以看到,我們已經完成了微服務數據庫模式的設計。現在,讓我們深入瞭解下微服務中的事件驅動架構。

9 事件驅動的微服務架構

本質上,事件驅動的微服務架構是指通過事件消息傳遞實現微服務之間的通信。在微服務異步通信那一節,我們已經從發佈 / 訂閱模式和 Kafka 消息代理系統中瞭解了這種方式。

我們說,通過事件驅動架構,我們可以實現異步行爲和松耦合的結構。例如,服務不是在需要數據時發送請求,而是通過事件來消費它們。這可以提高性能。

事件驅動的微服務架構也有很大的創新,比如使用實時消息平臺、流處理、事件中心、實時處理、批處理、數據智能等等。

因此,我們可以使這種事件驅動的方法更加通用,並通過實時事件處理特性來演進架構。

在這個新的事件驅動的微服務架構中,所有通信都是通過事件中心(Event-Hub)進行的。可以認爲,事件中心是一個可以完成實時處理的大型事件存儲數據庫。

架構設計——事件驅動的微服務架構

我們將利用事件驅動的微服務架構來設計我們的電子商務應用。

現在,讓我們確定下這個架構的技術棧。當然,我們應該選擇 Apache Kafka 作爲事件中心,將 Apache Spark 作爲實時和準實時流處理應用,對數據流進行轉換或迴應。

如你所見,現在我們用事件驅動的微服務架構實現了反應式設計。

現在,我們可以問同樣的問題:我們的設計可以滿足多大的併發請求?

這個最新的事件驅動的微服務架構(通過容器和編排器來部署),可以在低延遲的情況下滿足目標併發請求。

這個架構是完全松耦合的,並且做了高可擴展性和高可用性設計。

如你所見,我們完成了電子商務微服務架構的設計,這個過程涉及了所有的設計原則和模式。通過學習,你已經瞭解如何在設計中使用這些設計模式了,現在你可以設計自己的架構了。

原文鏈接:

https://medium.com/design-microservices-architecture-with-patterns/monolithic-to-microservices-architecture-with-patterns-best-practices-a768272797b2


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/o0g-S8lv52qd0lHUfQ6Bkg