SOA體系架構 微服務架構 微服務特點 微服務資料庫設計 傳統單一的中心化資料庫和微服務一個服務一個資料庫 微服務和限界上下文模式的關係 微服務的邏輯架構和物理架構 分散式數據管理的挑戰和解決方案 挑戰 1:如何定義微服務邊界 首先,需要關註應用的邏輯領域模型和相關數據。必須嘗試識別同一個應用中解耦 ...
SOA體系架構
- 面向服務的體繫結構 (SOA) ,通過將應用程式分解為多個服務(通常為 HTTP 服務,WCF服務等),將其分為不同類型(例如子系統或層),從而來劃分應用程式的結構。
- 微服務源自 SOA,但 SOA 不同於微服務體繫結構。 諸如大型中央代理、組織級別的中央業務流程協調程式和企業服務匯流排 (ESB) 等功能在 SOA 中很典型。 但在大多數情況下,這些是微服務社區中的反模式。
微服務架構
- 微服務體繫結構是一種將伺服器應用程式生成為一組小型服務的方法。
- 每個服務都在自己的進程中運行,並使用 HTTP/HTTPS、WebSocket 或 AMQP 等協議與其他進程進行通信。
- 每個微服務在特定的上下文邊界內實現特定的端到端域或業務功能,每個微服務都必須自主開發,並且可以獨立部署。
- 最後,每個微服務都應擁有其自己的相關域數據模型和域邏輯(主權和分散式數據管理),並且可以基於不同的數據存儲技術(SQL、NoSQL)和不同的編程語言。
- 在標識和設計微服務時,只要與其他微服務不存在過多的直接依賴項,就應嘗試讓它們儘可能地小。 比微服務的大小更重要的是,它必須具有內部內聚,並且獨立於其他服務。
- 單獨縮放需要更多處理能力或網路帶寬以支撐需求的功能區域,而不用橫向擴展應用程式中不需要縮放的其他區域。 這意味著節省成本,因為所需硬體更少。
微服務特點
- 對服務和基礎結構的監視和運行狀況檢查。
- 服務(即雲和業務流程協調程式)的可縮放基礎結構。
- 多個級別的安全設計和實現:身份驗證、授權、機密管理、安全通信等。
- 快速應用程式交付,通常不同的團隊重點負責不同的微服務。
- DevOps 和 CI/CD 實踐和基礎結構。
微服務資料庫設計
傳統單一的中心化資料庫和微服務一個服務一個資料庫
- 單一關係型資料庫的單體式應用有兩個重要優點:在應用程式的所有資料庫表和數據層面,都支持 ACID 事務和 SQL 語言。
- 如果多個服務訪問相同數據,數據的更新就要求協調同步到所有服務,這會破壞微服務生命周期的自治性。當業務流程跨越多個微服務時,最終一致性是唯一的辦法。
- 不同微服務通常使用不同種類的資料庫。一些場景下,NoSQL 資料庫比 SQL 資料庫有著更方便的數據模型和更好的性能與擴展性。
- 基於微服務的應用通常會混合使用 SQL 和 NoSQL 資料庫,這種做法有時會被稱為混合數據持久化(Polyglot Persistence)。 基於容器和微服務的應用架構 這種隔離的混合數據持久化架構有很多優點,包括服務的鬆散耦合,更好的性能、擴展性,更低成本和 可管理性等。
微服務和限界上下文模式的關係
- 微服務的理論源自領域驅動設計(DDD)的限界上下文(BC)模式。DDD 將大型業務劃分成多個 BC,並明確它們的邊界,每個 BC 必須有自己的模型和資料庫。類似的,每個微服務也擁有跟自己相關的數據。
- 不同限界上下文的表示語言中的信息(主要是領域實體)可以有不同名稱,即使不同領域實體共用相同標識(即通過唯一 ID 從存儲中讀取實體)。例如在一個用戶資料的限界上下文中,用戶 User 領域實體可能跟訂單限界上下文的買家 Buyer 領域實體共用標識。
- 微服務與限界上下文非常類似,但微服務是一種分散式服務,每個微服務都以獨立進程的方式創建,並且必須使用先前提到的分散式協議,如 HTTP/HTTPS、WebSockets 或 AMQP。然而限界上下文模式並沒有明確一個 BC 到底是分散式服務或只是單體式部署的應用中的簡單邏輯邊界(例如常見的子系統)。
微服務的邏輯架構和物理架構
- 創建微服務並不要求必須使用某種技術,例如 Docker 容器就不是必需的。微服務能夠作為普通進程運行,微服務是一種邏輯架構。
- 即使微服務能夠以獨立的服務、進程或容器方式來實現,這種在業務微服務和物理服務或容器之間的同等性不是必要的,尤其是當我們需要創建一個由成百上千的服務組成的大型複雜應用時。系統的邏輯架構和邏輯邊界與物理或部署架構沒必要一一對應,雖然可行,但通常並不這樣做。
- 您或許已經確定了業務微服務或限界上下文,但不意味著最佳實現方式就是為每個業務微服務創建單獨的服務(例如作為一個 ASP.NET Web API)或單獨的 Docker 容器。沒有規定說每個業務微服務必須使用單獨服務或容器來實現。因此,業務微服務或限界上下文是一個邏輯架構,或許確實(也可以不是)與物理架構一致。重點在於,業務微服務或限界上下文必須自主地進行代碼和狀態的獨立版本控制、部署和擴展。
- 如圖所示,目錄業務微服務由多個服務或進程組成,它們可以是多個 ASP.NET Web API 或使用HTTP 或其他協議的服務。更重要的是,這些服務共用相同的數據,因為它們都結合在一個相同的業務領域里。
- 本例中的 Web API 服務和搜索服務使用了同一個數據源,它們共用相同的數據模型。所以在物理實現業務微服務時,對功能進行了拆分,以便能夠按需向上或向下擴展每個內部服務。例如 Web API 通常需要更多的運行實例,反之亦然。
- 簡而言之,微服務的邏輯架構通常與物理架構並不相同。
分散式數據管理的挑戰和解決方案
挑戰 1:如何定義微服務邊界
- 首先,需要關註應用的邏輯領域模型和相關數據。必須嘗試識別同一個應用中解耦後的數據孤島和不同的上下文。
- 每個上下文都可以有不同的商業語言(不同的業務術語),上下文的定義和管理應該獨立進行。在不同上下文中使用的術語和實體可能聽起來相似,但有時在特定上下文中的一個業務概念在另一個上下文中可能會被用於不同的目,甚至可能使用不同名稱。例如,同一個“用戶”,可以是身份或會員系統上下文中的“用戶”,是 CRM 中的“客戶”,甚至還是訂單上下文中的“買方”等。
- 識別多個應用上下文以及每個上下文所對應不同領域之間邊界的方法,也能用來識別每個業務微服務和相關領域模型和數據的邊界。
- 後面的“識別微服務的領域模型邊界”將詳細介紹識別方法和領域驅動設計。
挑戰 2:如何創建從多個微服務獲取數據的查詢
第二個挑戰在於:如何實現從多個微服務獲取數據的查詢,同時避免遠程客戶端和微服務之間不必要的通信。
例如一個移動 App 需要一個頁面來展示由購物籃、產品目錄和用戶身份微服務包含的用戶信息。
再如一個複雜的報表系統涉及到位於多個微服務的多個表。
適合的解決方案取決於查詢的複雜性。但無論如何都需要一種方式來聚合信息,以提高系統的通信效率。最流行的解決方案如下。
API 網關:對於從多個擁有不同資料庫的微服務進行的簡單數據聚合,推薦的方式是通過名為 API 網關的機制聚合微服務。然而使用這種模式時需要當心,它可能成為系統瓶頸,也可能違反微服務自治的原則。為了降低這些可能性,可以使用多個細粒度的 API 網關,每個網關主要面向系統的一個垂直“切片”即業務領域。
CQRS 查詢/讀取表:另一種聚合多個微服務數據的方案是物化視圖模式(Materialized View Pattern),這種方案會提前(在實際查詢發生前準備好非規範的數據)生成包含多個微服務數據的只讀表,並且這種表會使用適合客戶端應用需求的格式。
假設有一個移動 App 的界面,如果有一個資料庫,就可以使用一個 SQL 查詢獲取界面所需的全部數據,該查詢會針對多個表執行複雜的聯接。
但如果使用了分佈在不同微服務中的多個資料庫,就不能查詢這些資料庫並創建 SQL 聯接。此時複雜的查詢將變成巨大的挑戰。為此可以使用 CQRS 方案:在不同資料庫中創建一個只用作查詢的非規範表,這個表可以針對複雜查詢所需的數據專門設計,把應用界面所需的欄位和查詢表的欄位一一對應。這樣的查詢表還可以用在報表中。
這種方式不僅解決了最初的問題(如何跨微服務查詢和聯接),與複雜的 SQL 聯接語句相比還能進一步提升性能,因為應用所需的數據已經在查詢表裡了。當然,使用命令查詢職責分離(CQRS)的查詢/讀取表需要額外的開發工作,並且需要面對數據最終一致性問題。然而在需要高性能和高擴展性的協作場景(或者競爭場景,取決於視角)下,應該使用 CQRS 來處理多資料庫。
中心資料庫的“冷數據”:對於可能不需要實時數據的複雜報表和查詢,此時一種通用方案是將“熱數據”(微服務里的交易數據)導出為“冷數據”存儲到報表專用的大型資料庫中。這裡的中心資料庫系統可以是基於大數據的系統,如 Hadoop,也可以是數據倉庫,如 Azure SQL 數據倉庫,甚至可以是單獨的報表專用 SQL 資料庫(如果容量不是問題的話)。
需要註意的是,中心資料庫應該只用於不需要實時數據的報表查詢,作為事實數據源的原始更新和交易數據必須在微服務中。為了同步數據,可採用事件驅動通信(下一節會詳細介紹),或使用資料庫基礎結構提供的導入/導出工具。如果使用事件驅動通信的方式,整合流程將與上文提到的使用 CQRS 查詢表獲取數據的方式類似。
然而,如果應用在設計上需要不斷從多個微服務里進行聚合數據併進行複雜查詢,上述設計將變得非常糟糕,畢竟微服務之間應該儘可能地保持相互獨立(使用中心冷資料庫的報表分析系統除外)。通常在遇到此類問題後,我們也許會合併微服務。我們需要在每個微服務的自治式進化和部署,以及強依賴、高內聚和數據聚合之間進行權衡。
挑戰 3:如何在多個微服務之間實現一致性
如上文所述,每個微服務擁有的數據是私有的,只能通過微服務 API 來訪問。因此會遇到這樣一個挑戰:如何跨多個微服務保持一致性的同時實現端到端的業務邏輯。
為了分析這個問題,讓我們看看示例應用 eShopOnContainers 中的一個例子。目錄(Catalog)微服務維護著所有產品信息,包括價格。購物籃(Basket)微服務管理著用戶加入購物籃的產品臨時數據,包括添加到購物籃時的產品價格。當目錄服務中一個產品的價格發生變化後,購物籃中相同產品的價格也應該變化,另外,系統應該告訴用戶說購物籃里的某個產品的價格變了。
假如這個應用有一個單體式版本,當產品表中的價格發生變化時,產品子系統能夠簡單地使用 ACID 事務來更新購物籃表裡的價格。
然而在微服務應用里,產品表和購物籃表被各自的微服務所占有。任何微服務不應該在自己的事務中包含其他微服務的表或存儲,即使是直接查詢也是不可以的。目錄微服務不能直接更新購物籃表,因為購物籃表被購物籃微服務占有。要更新購物籃微服務,產品微服務應該使用基於非同步通信,如集成事件(消息和基於事件的通信)的最終一致性。
根據 CAP 理論,我們需要在可用性和強 ACID 一致性之間作出選擇。大多數微服務場景要求高可用性和高擴展性,而非強一致性。重要應用必須保持隨時線上,開發人員可以使用弱一致性或最終一致性的技術來做到強一致性。此外,ACID 風格或兩步提交事務不僅違背微服務原則,大多數 NoSQL 資料庫(如 Azure CosmosDB、MongoDB 等)也不支持兩步提交事務。然而,跨服務和資料庫維護數據的一致性非常重要。
因此,總結來說,解決這個問題的方法之一是,在微服務之間使用事件驅動通信和發佈訂閱系統來實現最終一致性。
挑戰 4:如何在多個微服務之間通信
跨微服務邊界的通信是一個真正的挑戰。這裡所謂的“通信”不是指該用什麼協議(HTTP 和 REST、AMQP、消息等),而是指該使用什麼樣的通信方式,尤其是如何耦合微服務。取決於耦合等級,在宕機發生時,會對系統產生不同程度的影響。
諸如微服務這樣的分散式系統中,有很多內容需要四處移動,組成分散式服務的諸多伺服器或主機等組件最終都可能面臨故障,部分宕機甚至大面積失效也會發生。面對這樣的分散式系統,需要在設計微服務和跨服務通信時就考慮到這些常見的風險。
舉例來說,假設客戶端應用發起了一個 HTTP API 請求,需要調用另一個獨立的微服務,例如訂單微服務,如果訂單微服務隨後在同一個請求/響應周期內使用 HTTP 調用另一個微服務,這就創建了一個HTTP 調用鏈條。聽起來也許合理,然而如果打算這樣做,有些重要的問題需要考慮:
- 阻塞和性能低下。由於 HTTP 的同步本質,最初的請求在所有內部 HTTP 請求全部完成前不會獲得響應結果。假設這樣的請求量在逐步增長,同時某個中間微服務的 HTTP 調用被阻塞,結果就是性能受到影響,並且整體擴展性由於額外 HTTP 請求的增加遇到幾何級增長的影響。
- 使用 HTTP 耦合微服務。業務微服務不應該被其他業務微服務耦合。理想情況下,它們不應該“知道”其他微服務的存在。如果我們的應用像示例一樣依靠耦合的微服務,幾乎無法實現每個微服務的自治。
- 任何微服務引起的宕機。如果實現使用 HTTP 調用的鏈式微服務,當任何一個微服務宕機(最終每個微服務都可能宕機)時,整個微服務鏈會掛掉。微服務系統應該設計成在部分宕機情況下儘可能地繼續正常運行。即使客戶端邏輯使用了越來越快和靈敏的重試機制,HTTP 調用鏈越複雜,實現基於 HTTP 的容錯策略過程就越複雜。
實際上,如果內部微服務採用上述鏈式 HTTP 請求來通信,可能會產生這樣一種爭議:這實際上是一種單體式應用,它的進程間是基於 HTTP 的,而沒有使用進程內通信機制。
因此,為了促進微服務的自治並獲得更高彈性,應該減少使用跨服務的鏈式請求/響應通信。建議微服務間的通信只使用非同步交互,例如使用基於消息或事件的非同步通信,或者使用與原始 HTTP 請求/響應獨立的 HTTP 輪詢(Polling)方式。