事件驅動的基於微服務的系統的架構注意事項
今天的 IT 系統正在生成、收集和處理比以往更多的數據。而且,他們正在處理高度複雜的流程(正在自動化)以及跨越典型組織邊界的系統和設備之間的集成。同時,預計 IT 系統的開發速度更快、成本更低,同時還具有高可用性、可擴展性和彈性。
爲了實現這些目標,開發人員正在採用架構風格和編程範式,例如微服務、事件驅動架構、DevOps 等。正在構建新的工具和框架來幫助開發人員實現這些期望。
開發人員正在結合事件驅動架構 (EDA) 和微服務架構風格來構建具有極強可擴展性、可用、容錯、併發且易於開發和維護的系統。
在本文中,我將討論使用這兩種架構風格構建這些系統時的架構特徵、複雜性、關注點、關鍵架構注意事項和最佳實踐。儘管 API、API 網關和 UI 等組件在架構上很重要,但在本文中我將主要關注事件驅動的微服務。
事件驅動架構和微服務架構概述
事件驅動架構(EDA)已經存在了很長時間。雲、微服務和無服務器編程範式以及複雜的開發框架正在提高 EDA 在實時解決關鍵任務業務問題中的適用性。Kafka、IBM Cloud Pak for Integration 和 Lightbend 等技術和平臺以及 Spring Cloud Stream、Quarkus 和 Camel 等開發框架都爲 EDA 開發提供一流的支持。EDA 還擴展到流數據處理,這是開發實時人工智能或機器學習解決方案的要求。文章 “事件驅動架構的優勢” 定義了 EDA 並解釋了爲什麼開發人員應該使用它。
微服務架構正在被廣泛採用並用於轉型項目,這些項目涉及將單體應用程序分解爲使用域驅動設計識別的自包含、獨立部署的服務。Martin Fowler 和 James Lewis 的文章很好地介紹了微服務架構及其特性。微服務可以公開 API,並具有用於生成和使用事件的接口,以與 EDA 無縫集成。它的許多特性使其成爲將其與 EDA 相結合的理想選擇。文章 “微服務架構風格的挑戰和好處” 討論了開發人員在實現微服務時面臨的挑戰。
下表顯示了這兩種架構風格如何相互補充:
通過結合這兩種架構風格,開發人員可以構建分佈式、高度可擴展、可用、容錯和可擴展的系統。這些系統可以實時消費、處理、聚合或關聯極其大量的事件或信息。開發人員可以使用行業標準的開源框架和雲平臺輕鬆擴展和增強這些系統。
架構問題和複雜性
通過結合 EDA 和微服務架構風格,開發人員可以輕鬆實現非功能性品質,例如性能、可擴展性、可用性、彈性和易於開發。然而,這兩種架構風格也引入了一些主要問題。
其中一些擔憂包括:
-
大量分佈式獨立部署的組件或服務,其中引入了這些問題:
-
設計和實現的複雜性。理解和調試這樣的系統是困難的。事件處理工作流程不直觀,需要記錄在案。
-
多點故障。增加了測試、調試和異常處理的複雜性。
-
發佈過程、部署和系統監控變得複雜,需要高度自動化。
-
從開發的角度來看,需要實現的一致性、設計的一致性和實現標準。但是,有多個開發小組。這可能導致不一致的實施和質量問題。因此,開發一個參考架構,概述架構模式的使用、開發框架、可重用服務或實用程序的開發,以及建立一個健壯和有效的治理模型是必不可少的。
-
由於與事件排序或排序、回調和異常處理相關的要求,與同步處理相比,異步事件處理比較困難。
-
丟失信息或事件是不可取的(顯然)。因此,對具有極高可用性、可擴展性和容錯性的系統的要求尤爲重要,這使得系統的設計和部署變得相當複雜。事件生產者和消費者的設計必須能夠承受故障,能夠重放失敗的事件,並具有重複數據刪除功能。
-
缺乏對分佈式事務的支持。這個問題意味着開發人員必須創建跨多個分佈式系統的自定義和複雜的回滾和恢復實現。
-
保持數據一致性。由於分佈式特性和多個記錄系統,維護數據一致性很複雜。在大多數情況下,由於缺乏跨多個分佈式系統的原子事務,它是最終的一致性。
-
事件消費者和生產者必須考慮特定於用於事件代理、數據緩存等的產品的屬性。例如,交付保證會影響生產者和消費者的設計。
EDA - 微服務系統的架構藍圖
下圖是一個基於 EDA - 微服務的企業系統的架構圖。一些微服務組件和類型單獨顯示,以使架構更清晰。
此藍圖中的 EDA 和特定於微服務的組件是:
-
事件主幹。事件主幹主要負責事件的傳輸、路由和序列化。它可以提供用於處理事件流的 API。事件主幹提供對多種序列化格式的支持,並對架構質量(例如容錯、彈性可伸縮性、吞吐量等)產生重大影響。也可以存儲事件以創建事件存儲。事件存儲是恢復和彈性的關鍵架構模式。
-
服務層。服務層由微服務、集成以及數據和分析服務組成。這些服務通過各種接口公開其功能,包括 REST API、UI 或作爲 EDA 事件生產者和消費者。服務層還包含特定於 EDA 並解決橫切關注點的服務,例如編排服務、流數據處理服務等。
-
數據層。數據層通常由兩個子層組成。在此藍圖中,未顯示微服務擁有的各個數據庫。
-
緩存層,提供分佈式和內存數據緩存或網格,以提高性能並支持 CQRS 等模式。它是水平可擴展的,並且還可能具有一定程度的複製和持久性以實現彈性。
-
大數據層,由數據倉庫、ODS、數據集市、AI/ML 模型處理組成。
-
微服務底盤。微服務底盤提供系統不同層所需的必要技術和橫切服務。它提供開發和運行時功能。通過使用微服務機箱,您可以降低設計和開發的複雜性以及運營成本,同時縮短上市時間、交付質量和大量微服務的可管理性。
-
部署平臺:應使用彈性、成本優化、安全且易於使用的雲平臺。開發人員應儘可能多地使用 PaaS 服務,以減少維護和管理開銷。該架構還應提供混合雲設置,因此應考慮 Red Hat OpenShift 等平臺。
主要架構考慮因素
架構方面的考慮會影響系統的架構。它們充當制定架構決策的指南。它們對系統的非功能特性有重大影響。以下架構注意事項對於事件驅動、基於微服務的系統極爲重要:
-
架構模式
-
技術棧
-
事件建模
-
處理拓撲
-
部署拓撲
-
異常處理
-
利用事件主幹功能
-
安全
-
可觀察性
-
容錯和響應
架構模式
選擇架構和集成模式是事件驅動、基於微服務的系統的關鍵架構考慮因素。它們爲許多所需的架構質量提供經過驗證和測試的解決方案。以下架構模式在開發事件驅動、基於微服務的系統中非常有用:
-
管道和過濾器
-
分階段事件驅動架構 (SEDA)
-
事件溯源
-
命令查詢職責分離 (CQRS)
-
Saga
-
流處理
-
微服務底盤
-
死信隊列 (DLQ)
此外,許多企業集成模式和微服務模式爲基於事件驅動的微服務系統提供了構建塊。
需要根據系統所需的需求和架構質量來選擇模式。
技術棧
事件代理、數據緩存或網格、微服務框架、安全機制、分佈式數據庫、監控系統和警報系統等組件構成了事件驅動、基於微服務的系統的技術骨幹。該主幹爲關鍵架構質量(性能、可用性、可靠性、運營成本、容錯性等)提供支持並簡化了開發。它還影響一些設計和開發決策。
在選擇您的技術堆棧時,請考慮以下特徵:
-
單個組件的水平可擴展性。擴展不應損害可用性。也就是說,節點的添加不應該需要停機時間。
-
單個組件的高可用性。所選產品或框架應支持集羣,具有跨不同可用區或區域的成員的能力,支持滾動升級,支持數據複製,並且應該是容錯的,這意味着集羣應該在節點丟失的情況下重新平衡自身。
-
雲親和力,這意味着它應該很容易部署在雲上。事實上,如果它們在 PaaS 平臺上作爲服務提供,那就更好了,因爲它減少了管理和維護開銷。必須支持容器化。
-
運營成本低,這意味着它應該能夠在商品硬件上運行,並且在 CPU、內存和存儲方面應該節儉。
-
無需停機即可對行爲和非功能特性進行配置和調整。
-
可管理性。
-
應避免供應商鎖定。選擇基於開放標準或開源產品的產品。在選擇開源產品時,要考慮產品被廣泛採用的程度,是否有一個蓬勃發展的開發者社區,以及許可證應該是開放的而不是非常嚴格的(例如 Apache License V2.0)。
-
對於事件代理和開發框架,它們應該支持:
-
多種序列化格式(JSON、AVRO、Protobuf 等)
-
異常處理和死信隊列 (DLQ)
-
流處理(包括對聚合、連接和窗口化的支持)
-
分區和保持事件的順序
-
反應式編程支持很不錯。
-
多語言編程支持在 Event 主幹中很不錯。
下表列出了不同組件的流行選擇:
事件建模
事件建模包括定義事件類型、事件層次結構、事件元數據和有效負載模式。仔細考慮這些事件建模特徵:
-
事件類型。在企業系統中,有多個業務領域,每個領域都在消費和產生不同類型的事件。建模的關鍵方面之一是識別事件類型和事件。使用領域驅動的設計和實踐(例如事件風暴和事件源)來識別和分類事件。事件類型本質上可以是分層的,這有助於採用分層的事件處理方法。定義事件類型和事件以涵蓋所有業務需求並將它們映射到不同的業務流程或工作流。事件類型的粒度對於避免組件之間的緊密耦合至關重要。事件類型是定義路由規則的關鍵。
-
事件架構。事件模式由事件元數據(例如類型、時間、源系統等)和用於事件處理器處理的有效負載(即信息)組成。事件類型通常用於路由。事件元數據通常用於關聯和排序事件,但它也可用於審計和授權目的。有效負載會影響隊列、主題和事件存儲的大小、網絡性能、(反)序列化性能和資源利用率。避免重複內容。您始終可以通過在需要時重播事件來重新生成狀態。
-
版本控制。需求和實現會隨着時間的推移而發展,它們通常會影響事件模型。對事件模型的更改可能會影響太多的微服務。同時更改所有受影響的服務是不切實際的。因此,事件模型應該支持多個版本並向後兼容,以便微服務可以在他們方便的時候進行更改。向有效負載添加新屬性而不是更改現有屬性(棄用而不是更改)也是一個好主意。版本控制取決於序列化格式。
-
序列化格式。有多種序列化格式可用於對事件及其有效負載進行編碼,例如 JSON、protobuf 或 Apache Avro。這裏的重要考慮因素是模式演變支持、(反)序列化性能和序列化大小。由於事件消息是人類可讀的,因此開發和調試 JSON 非常容易,但 JSON 性能不高,可能會增加事件存儲要求。雖然 Avro 或 Protobuf 減小了有效負載的大小,速度很快,並且支持模式演變,但它們需要額外的設計和開發工作。
-
分區。事件的分區對於提高併發性、可伸縮性和可用性很重要。分區也是消息排序的關鍵。從架構的角度來看,選擇分區鍵很重要。擁有一個非常粗粒度的密鑰會影響可伸縮性和併發性。擁有一個非常細粒度的密鑰可能無助於保持事件的順序。在 Kafka 等事件代理中,分區限制了事件消費者的可伸縮性。
-
訂購。某些事件可能需要根據它們的到達時間進行排序(至少對於給定實體而言)。例如,必須按順序處理給定帳戶的帳戶交易。識別需要排序的事件很重要。僅在必要時才應使用排序,因爲它會影響性能和吞吐量。在 Apache Kafka 中,事件的順序與分區直接相關。
-
事件持久性持久性是指事件在隊列或主題上可用多長時間。例如,您是否應該在使用事件後立即刪除它。刪除早於配置的保留期的事件。刪除具有顯式標記的事件(例如 Kafka 中的墓碑)。根據要求,應選擇並配置其中之一。在使用基於時間的保留時,如果需要,請考慮事件應可用於重播多長時間。如果正在使用事件存儲模式,則必須考慮有關需要維護的同一事件或有效負載的版本數量的附加問題。Kafka 等事件代理提供了各種配置選項,可以在主題級別進行設置,以指定事件的持久性。
事件處理拓撲
在 EDA 中,處理拓撲是指對生產者、消費者、企業集成模式以及主題和隊列的組織,以提供事件處理能力。它們基本上是事件處理管道,其中部分功能邏輯(處理器)使用企業集成模式、隊列和主題連接在一起。處理拓撲是 SEDA、EIP 和 Pipes & Filter 模式的組合。對於複雜的事件處理,多個處理拓撲可以相互連接。
處理拓撲中的另一個關鍵概念是編排與編排。編排是指擁有一箇中央編排器,通過調用不同的組件來編排處理工作流。每當需要對處理進行嚴格控制時,都會選擇編排,例如用於支付處理。編排通常用於採用 SAGA 模式的地方。編排需要在性能和可用性之間進行權衡(因爲編排器可能會成爲單點故障)。編舞指完全去中心化的處理方式。也就是說,事件被髮布並且感興趣的組件訂閱主題。沒有中央組件來控制處理流程。編排的實現和維護很複雜。
請考慮以下有關創建處理拓撲的指南:
-
處理階段(處理器)應使用持久隊列和主題連接。
-
在每個隊列或主題上配置分區鍵和消息保留策略。
-
處理的粒度很重要。如果處理器的粒度太細,那麼處理器之間就有可能緊密耦合。理想情況下,每個處理器應該在邏輯上彼此獨立。
-
微服務可用於實現處理器。這允許鬆散耦合、職責分離和易於開發。
-
處理併發應該可以在處理器級別進行配置。
-
使用經過驗證的企業集成模式 (EIP)。選擇爲 EIP 提供內置支持的開發框架,例如 Apache Camel 或 Spring Cloud Stream。
-
構建模塊化和分層處理拓撲,以便通過組裝簡單的處理管道來實現複雜的事件處理。這有助於使實現模塊化且易於更新。
-
如果處理器具有狀態(隨事件而變化),請考慮使用存儲來支持狀態,以提高容錯性和可恢復性。
可以使用流程事件流和事件管理狀態等架構實踐來設計處理拓撲。在定義處理拓撲時詳細瞭解事件代理功能也很好。例如,Kafka 流爲定義事件流處理拓撲提供了一流的支持。當對事件流執行聚合和連接操作時,Kakfa 還提供對狀態存儲的自動支持。
下圖描繪了處理拓撲的藍圖。
下圖描述了在線購物的簡化訂單處理拓撲。路由器能夠動態地將事件路由到多個主題。另請注意,事件處理器還將具有 “事件過濾器”,以根據上下文控制事件的消費和生產。
部署拓撲
在 EDA 微服務架構中,需要部署許多組件。應該選擇一個部署拓撲,以便滿足與可伸縮性、可用性、彈性、安全性和成本相關的架構要求。但是,需要在冗餘、性能和成本之間進行權衡。部署到雲使架構更具性能、彈性和成本效益。應利用雲部署(例如 high availabilityKubernetes 中的設置)提供的功能。
考慮爲您的部署拓撲考慮以下關鍵原則:
-
每個已部署的組件都應可獨立擴展並部署爲集羣,以提高併發性和彈性。
-
確保每個集羣跨越多個可用區。這種設置在數據中心出現故障的情況下提供了更大的彈性。這樣做的另一個好處是,可以跨不同的可用區或區域進行主動 - 主動部署,而不是被動 DR。
-
複製因子決定了事件或信息的複製數量。如果沒有複製,單個實例的故障(即使是集羣的)也會導致數據丟失。這對於事件代理和數據庫尤其需要。然而,複製是以計算和存儲爲代價的。應根據可用區、數據區域、節點數等因素設置複製。
-
在 Kafka 的情況下,主題分區的數量對消費者的併發性設置了上限。
-
工作負載節流。配置線程池以及消費者和生產者的實例數量以限制吞吐量。根據下游處理器的容量和吞吐量,需要相應地調整這些參數。
-
數據壓縮。如果有效負載大小很大並且 CPU 可用性很高,則可以使用壓縮來壓縮傳輸前的事件。但是,壓縮是網絡利用率和 CPU 利用率之間的權衡。
-
數據加密。根據組織中的安全標準,在事件代理與生產者和消費者(以及您的數據庫)之間配置 TLS、身份驗證和授權。請注意,啓用 TLS 會增加 CPU 利用率。
此外,支持自動部署、自動故障轉移、滾動升級或藍綠部署以及配置的外部化以使部署工件的環境獨立,這一點很重要。
異常處理策略
在 EDA 中,擁有全面且一致的異常處理策略對於提高彈性非常重要。異常處理策略由以下全部或部分組成:
-
記錄異常
-
在指定的時間和指定的重試間隔內重試事件
-
如果所有重試都用盡,則將事件移動到死信隊列(或停止事件處理)
-
發出警報
-
在某些情況下會產生事件
-
糾正異常原因並重放事件
異常可以有兩種類型:業務異常和系統異常。當驗證或業務條件失敗時會引發業務異常。系統異常是由於組件(數據庫、事件代理或其他微服務)不可用或由於資源問題(例如 OutOfMemory 錯誤)、網絡或傳輸相關問題(例如有效負載序列化或反序列化錯誤)而導致的廣泛故障類別,或意外的代碼故障(例如 NullPointerException 或 ClassCastException)。
處理不同類型異常的方式存在顯着差異。下面列出了一些異常處理機制:
-
預期的業務異常通常在代碼中處理。處理可能涉及記錄異常、更新實體及其狀態、生成異常事件或使用異常並繼續前進。
-
由於無效負載(包括序列化或反序列化問題)導致的異常將無法通過重試來解決。此類事件在 Kafka 中被稱爲 poision pills(因爲它阻塞了該分區的後續消息)。此類事件可能需要干預。建議將它們移動到死信隊列 (DLQ)。DLQ 消費者應該允許更正和重播事件。
-
由於組件不可用而導致的系統異常本質上是暫時的。因此,應配置多次重試。另一個關鍵配置參數是退避乘數。它用於在連續重試之間具有指數增加的時間間隔。如果重試後失敗仍然存在,不同的框架有不同的策略。例如,Camel 會將事件移動到 DLQ。Kafka 流將停止處理。建議在這種情況下使用框架的默認行爲。
-
資源問題(例如 OutOfMemory 錯誤)通常在組件級別,會導致組件不可用。由於事件代理的容錯特性,這裏丟失事件的風險很小。此外,當部署在 Kubernetes 環境中時,會啓動新的 pod 來替換失敗的 pod。
-
SAGA 模式用於數據一致性非常重要且處理涉及多個微服務的情況。對數據一致性要求非常嚴格的事件使用 SAGA 模式。
-
應該從一開始就考慮恢復和重放,而不是事後才應用(以後會變得非常複雜)。恢復和重放組件通常是定製開發的,並且會根據事件處理而有所不同。最簡單的重播組件可能只是拾取失敗的事件並將其重新發布到輸入主題。
您的開發框架應該支持在所有微服務中使用一致的異常處理策略。它應該爲業務異常提供一組預定義的異常類,並提供一個通用的異常處理程序,該處理程序可以使用配置進行定製,但強制執行與異常處理相關的架構決策。大多數開發框架確實提供了這種支持。但是,它們需要正確配置或擴展以提供所需的功能。
事件主幹能力和約束
不同的事件骨幹產品或平臺爲架構質量提供不同的支持。同時,它們對設計和架構施加了限制。在定義架構時,應考慮其能力和約束以有效解決非功能性需求。例如,以下是 Kafka 的一些重要功能和約束。
-
Kafka 支持基於分區鍵的事件排序。它還確保有一個消費者(線程)在一個分區上監聽。這使得只需選擇適當的分區鍵就可以很容易地對事件進行排序。例如 OrderId,當用作分區鍵時,將確保與特定訂單相關的所有事件都將按照它們到達的順序進行處理。
-
Kafka 支持 idempotence 生產者。這意味着 Kafka 確保一個事件由生產者只生產一次。開發人員無需擔心。
-
Kafka 提供 at least once 交付保證。這意味着消費者應該能夠處理重複的消息。開發人員需要了解他們的事件代理提供的保證。
-
Kafka 的另一個重要方面是 offset-commit 消費者策略,這意味着事件應該是自動確認還是手動確認。如果啓用了自動提交,則產生錯誤的事件可能會丟失(如果消耗了異常),或者使用者可能會看到重複的消息。手動提交可以用來解決這個問題,但它需要額外的代碼。auto-committing 除了手動 / 自動提交之外,與 Kafka 無縫協作的框架(例如 spring-cloud-stream)提供了在發生錯誤時不處理或將失敗事件移動到 DLQ 的選擇。這是設計過程中需要考慮的一個重要方面。
-
Kafka Streams 提供了處理事件流的能力,並且可以輕鬆地對事件流執行各種高級和複雜的操作,例如聚合和連接。這使得實時執行分析變得非常容易。例如,計算按不同維度分組的事件的實時統計數據需要非常少的編碼。這些是有狀態的操作並保持狀態。Kafka 還通過 state-stores 提供自動容錯。
安全
開發人員必須考慮 EDA 微服務架構中的這些安全方面:
-
運輸級安全
-
對事件生產和消費的認證和授權訪問
-
事件處理的審計跟蹤
-
數據安全(如授權訪問和加密存儲)
-
消除代碼中的漏洞
-
周邊安全設備和模式
可觀察性
可觀察性包括監控、記錄、跟蹤和警報。系統的每個組件都應該是可觀察的,以避免故障並從故障中快速恢復。
大多數 EDA 產品和開發框架通過發佈可導出到 Prometheus 和 Grafana、ELK、StatsD 和 Graphite、Splunk 或 AppDynamics 等行業標準可觀察性工具的指標來提供對可觀察性的支持。例如,Apache Kafka 提供了可以導出並與大多數這些工具集成的詳細指標。此外,爲事件主幹 (IBM Event Streams) 提供託管服務的雲平臺爲可觀察性提供一流的支持。Spring 或 Camel 等微服務開發框架爲代碼檢測提供了良好的支持以進行監控。
從 EDA 的角度來看,檢測生產者和消費者的代碼以發佈指標、發佈事件代理指標並通過指標儀表板關聯這些是必不可少的,因爲 EDA 中分佈式組件的數量很多。從 EDA 的角度來看,一些關鍵指標是傳入和傳出消息的速率、消費滯後、網絡延遲、隊列和主題大小等。
有關監控微服務,請參閱我的監控 Spring Boot 微服務教程,瞭解有關檢測和監控微服務的詳細信息。
容錯和響應
爲了提供足夠的容錯能力,架構需要提供冗餘、異常處理和彈性伸縮(當超出閾值時放大,當負載恢復正常時縮小)。藉助 EDA 和雲,其中大部分都可以輕鬆實現。事件主幹通過支持隊列和主題的集羣和複製來滿足容錯。生產者和消費者可以部署多個實例。當在 Kubernetes 平臺上部署爲容器時,可以通過自動縮放(使用水平 pod 自動縮放器)輕鬆實現彈性縮放,但必須爲生產者和消費者設計異常處理。
儘管基於 EDA 的系統通過分級架構提供彈性,但快速故障響應和恢復對於避免延遲和一致性問題至關重要。要實現這種快速恢復,您需要:
-
用於啓動和停止實例以及重新啓動失敗實例的自動化,可以在基於 Kubernetes 的平臺(例如 Red Hat OpenShift)中輕鬆配置
-
在發生故障時發出警報和事件
-
定義明確的事件管理流程
-
日誌的可用性以及通過跟蹤跨多個組件關聯日誌的能力。需要在微服務中啓用跟蹤。可以使用諸如 spring-sleuth 之類的開發框架。對於日誌聚合,可以使用 ELK 或 Splunk 等工具。這將有助於團隊確定根本原因並快速解決問題。
結論
開發者可以結合事件驅動架構和微服務架構風格來開發分佈式、高可用、容錯和高吞吐量的系統。這些系統可以處理非常大量的信息,並且可以具有極高的可擴展性。然而,在構建此類系統時,開發人員必須考慮許多架構問題和複雜性,並做出許多關鍵的架構決策。在本文中,討論了關鍵的架構決策以及做出這些決策需要考慮的因素。通過遵循本文中的指導,可以定義一個健壯的 EDA 微服務架構來實現預期的目標。
來源:
https://www.toutiao.com/a7058632639380472357/?log_from=eae0ef2ae2b7e_1644802188555
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8kKTtdbNXabKA8h7sehVJA