基于Event Sourcing和CQRS的微服務架構設計與實戰

封面

基于Event Sourcing和CQRS的微服務架構設計與實戰

業務場景描述

在電商系統中,訂單的高并發寫入與復雜的狀態流轉(下單、支付、發貨、退貨等)給傳統的CRUD模型帶來了挑戰:

  • 數據一致性難保證:跨服務事務處理復雜,分布式事務開銷大。
  • 寫放大問題:頻繁更新導致熱點寫入及性能瓶頸。
  • 審計和追溯需求:需要完整的訂單狀態變更歷史。

針對上述痛點,我們引入Event Sourcing(事件溯源)與CQRS(命令查詢職責分離)來構建高可用、可追溯、易擴展的訂單微服務。

技術選型過程

  1. Event Sourcing:將狀態變化記錄為不可變事件,完整保留歷史。優點是天然可審計、可回溯,但事件存儲和重播需要額外設計。
  2. CQRS:將寫模型(Command)與讀模型(Query)分離,寫入事件后異步同步或投影至專門的查詢存儲,提高讀寫性能。缺點是最終一致性帶來的復雜性。
  3. 消息中間件:選擇Kafka作為事件總線,提供高吞吐與持久保證。
  4. 存儲:事件存儲使用關系型數據庫(PostgreSQL + EventStore表),查詢存儲使用Elasticsearch,以滿足復雜搜索與報表需求。

綜合考慮,系統采用:Spring Boot + Spring Cloud 構建微服務;Event Sourcing + CQRS;Kafka 事件總線;PostgreSQL 事件表;Elasticsearch 查詢庫。

實現方案詳解

項目結構(簡化)

order-service/
├── cmd-api/           // Command 側 REST 接口
├── cmd-impl/          // Command 處理、Event Sourcing 模塊
├── query-service/     // Query 側服務(Spring Data + ES)
├── common/            // 共享模型和工具包
└── config/            // 配置中心、Spring Cloud Config

1. 事件定義

// OrderCreatedEvent.java
public class OrderCreatedEvent {private String orderId;private BigDecimal amount;private LocalDateTime createdTime;// getter/setter
}// OrderStatusChangedEvent.java
public class OrderStatusChangedEvent {private String orderId;private String fromStatus;private String toStatus;private LocalDateTime occurredTime;// getter/setter
}

2. 聚合與Command處理

@Service
public class OrderAggregate {@Aggregateprivate String orderId;private String status;@CommandHandlerpublic OrderAggregate(CreateOrderCommand cmd) {// 校驗if (cmd.getAmount().compareTo(BigDecimal.ZERO) <= 0) {throw new IllegalArgumentException("訂單金額必須大于0");}// 發布事件apply(new OrderCreatedEvent(cmd.getOrderId(), cmd.getAmount(), LocalDateTime.now()));}@CommandHandlerpublic void handle(ChangeOrderStatusCommand cmd) {apply(new OrderStatusChangedEvent(orderId, this.status, cmd.getNewStatus(), LocalDateTime.now()));}@EventSourcingHandlerpublic void on(OrderCreatedEvent evt) {this.orderId = evt.getOrderId();this.status = "CREATED";}@EventSourcingHandlerpublic void on(OrderStatusChangedEvent evt) {this.status = evt.getToStatus();}
}

3. Kafka配置(application.yml)

spring:kafka:bootstrap-servers: ${KAFKA_SERVERS}producer:key-serializer: org.apache.kafka.common.serialization.StringSerializervalue-serializer: org.springframework.kafka.support.serializer.JsonSerializerconsumer:key-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.springframework.kafka.support.serializer.JsonDeserializerproperties:spring.json.trusted.packages: "*"

4. 讀模型投影

@Service
public class OrderProjection {@EventListenerpublic void handle(OrderCreatedEvent evt) {OrderIndex idx = new OrderIndex(evt.getOrderId(), evt.getAmount(), evt.getCreatedTime(), "CREATED");orderIndexRepository.save(idx);}@EventListenerpublic void handle(OrderStatusChangedEvent evt) {OrderIndex idx = orderIndexRepository.findById(evt.getOrderId()).orElseThrow();idx.setStatus(evt.getToStatus());orderIndexRepository.save(idx);}
}

Elasticsearch實體:

@Document(indexName = "order_index")
public class OrderIndex {@Id private String orderId;private BigDecimal amount;private LocalDateTime createdTime;private String status;// constructor/getter/setter
}

5. API示例

// 創建訂單
@PostMapping("/orders")
public ResponseEntity<String> create(@RequestBody CreateOrderDTO dto) {commandGateway.send(new CreateOrderCommand(dto.getOrderId(), dto.getAmount()));return ResponseEntity.accepted().body("創建成功");
}// 查詢訂單
@GetMapping("/orders/{id}")
public Mono<OrderIndex> get(@PathVariable String id) {return orderIndexRepository.findById(id);
}

踩過的坑與解決方案

  1. 事件順序亂序:Kafka多分區導致同一訂單事件投遞順序不一致。解決:指定訂單ID為分區鍵,保證同一Key事件有序。
  2. 投影臟讀:事件尚未投影完成前查詢不到數據。解決:業務可加重試機制或在響應中返回Location,讓客戶端輪詢獲取。
  3. 事件庫膨脹:歷史事件表過大影響查詢。解決:定期歸檔老事件或冷表分區清理策略。
  4. 聚合重放性能:啟動時重放全量事件過慢。解決:采用快照(Snapshot)機制定期保留最新狀態,以快照為起點加載。

總結與最佳實踐

  • Event Sourcing+CQRS模式適用于高并發、復雜狀態流轉、強審計需求場景。
  • 讀寫分離提升性能,但帶來最終一致性,需要在業務層做好補償。
  • 采用分區鍵、快照、歸檔等手段優化性能與存儲。
  • 強烈建議構建完善的監控和可視化工具,如使用Prometheus監控事件延遲、投影時長。

通過本實戰示例,您可以快速上手Event Sourcing和CQRS在微服務中的落地,并在生產環境中規避常見坑,實現高可用、高性能的系統架構設計!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92064.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92064.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92064.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

初級安全課第二次作業

&#xff08;一&#xff09;xss-labs 1~8關 1、前期準備 &#xff08;1&#xff09;打開小皮面板&#xff0c;并啟動Apache和MySQL&#xff08;2&#xff09;將 xss-labs放到 phpstudy_pro 的 WWW 目錄下&#xff08;3&#xff09;訪問連接&#xff1a;http://localhost/xss-la…

從零搭建智能搜索代理:LangGraph + 實時搜索 + PDF導出完整項目實戰

傳統的AI聊天系統往往局限于預訓練數據的知識范圍&#xff0c;無法獲取實時信息。本文將詳細闡述如何構建一個基于LangGraph的智能代理系統&#xff0c;該系統能夠智能判斷何時需要進行網絡搜索、有效維護對話上下文&#xff0c;并具備將對話內容導出為PDF文檔的功能。 本系統…

C語言分支和循環語句——猜數字游戲

分支語句的語法形式1. if(表達式)語句;2. if(表達式)語句1;else語句2;3. Switch(表達式){ case 1: break;case 2: break;case 3: break; default: break; }循環語句的語法形式1. while(表達式)語句 ;2. for&#xff08;表達…

Python設計模式深度解析:原型模式(Prototype Pattern)完全指南

Python設計模式深度解析&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09;完全指南前言什么是原型模式&#xff1f;模式的核心組成實際案例&#xff1a;游泳比賽管理系統游泳者數據結構原型模式的實現深拷貝 vs 淺拷貝&#xff1a;核心概念解析淺拷貝&#xff08…

SAP-ABAP:SAP萬能長度計算:DYNAMIC_OUTPUT_LENGTH 深度解析

&#x1f4cf; SAP ABAP 萬能長度計算&#xff1a;DYNAMIC_OUTPUT_LENGTH 深度解析核心作用&#xff1a;智能計算數據對象在列表/ALV中的實際顯示寬度 | 關鍵優勢&#xff1a;多字節字符處理 | 格式感知 | 動態適配&#x1f50d; 一、核心功能與技術特性 &#x1f4ca; 數據類型…

20250720-2-Kubernetes 調度-資源限制對Pod調度的影響(1)_筆記

一、創建一個Pod的工作流程&#xfeff;1. k8s架構解析&#xfeff;組件交互模式: Kubernetes采用list-watch機制的控制器架構&#xff0c;實現組件間交互的解耦。各組件通過監控自己負責的資源&#xff0c;當資源發生變化時由kube-apiserver通知相關組件。類比說明: 類似小賣鋪…

mobaxteam x11傳輸界面避坑

mobaxteam x11傳輸界面避坑 文章目錄mobaxteam x11傳輸界面避坑1 windows系統必須下載xing2 配置1 windows系統必須下載xing 因為windows系統本身沒有x服務。 2 配置 如圖

flink sql如何對hive string類型的時間戳進行排序

在 Flink SQL 中對 Hive 表的 STRING 類型時間戳進行排序&#xff0c;需要先將字符串轉換為時間類型&#xff0c;再基于時間類型排序。以下是具體方法和示例&#xff1a; 一、核心解決方案 1. 字符串轉 TIMESTAMP 后排序 若 Hive 中的時間戳格式為 yyyy-MM-dd HH:mm:ss&#xf…

Linux:線程控制

線程概念線程&#xff08;Thread&#xff09;是進程&#xff08;Process&#xff09; 中的一個執行單元&#xff0c;是操作系統能夠進行運算調度的最小單位。線程也被稱為“輕量級進程”&#xff08;Lightweight Process, LWP&#xff09;。一個進程可以包含多個線程&#xff0…

React 學習(4)

核心API———createRoot、render方法1.createRoot 方法是創建react的根容器&#xff0c;就是react元素的插入位置&#xff0c;插入的dom會被轉化成react元素&#xff0c;根容器內的內容都會被react管理&#xff0c;原有dom都會被刪除。react17 根容器創建、渲染方式&#xff0…

ASP .NET Core 8集成Swagger全攻略

Swagger (現在稱為 OpenAPI) 是一個用于描述 RESTful API 的規范&#xff0c;ASP.NET Core 內置支持通過 Swashbuckle 庫生成 Swagger 文檔。以下是在 ASP.NET Core 8 中實現 Swagger 的完整步驟。1、添加Swagger NuGet 包dotnet add package Swashbuckle.AspNetCore2、添加Swa…

【iOS】源碼閱讀(六)——方法交換

文章目錄方法交換什么是Method-Swizzling方法交換核心API**1. 獲取方法對象****2. 添加/替換方法實現****3. 交換方法實現****4. 獲取方法信息****5. 修改方法實現****使用示例&#xff1a;完整的 Method-Swizzling 流程****注意事項**使用方法交換注意事項線程安全方法交換的影…

mysql運維問題解決:MySQL主從延遲(鎖阻塞與讀寫分離)

小亦平臺會持續給大家科普一些運維過程中常見的問題解決案例&#xff0c;運維朋友們可以在常見問題及解決方案專欄查看更多案例 問題概述 告警事件&#xff1a; 2023-07-28 03:31:39.571 首次觸發主從延遲告警&#xff08;延遲1515秒&#xff09;2023-07-28 07:41:37 告警解除…

SSH 密鑰

什么是 SSH 密鑰 SSH 密鑰就像是你電腦的“身份證”和“鑰匙”&#xff0c; 用來安全登錄另一臺電腦&#xff08;服務器&#xff09;&#xff0c;而不需要每次輸入密碼。SSH 密鑰是一種安全登錄遠程服務器的方式&#xff0c;由一對加密的“鑰匙”組成&#xff1a;一個公鑰 一個…

st-Gcn訓練跳繩識別模型一:數據標注工具和標注流程

目錄 工具展示和使用說明 工具標注后文件展示說明 json轉換成單個npy文件 數據獲取補充 工具展示和使用說明 文件名labelV.py集于PySide6實現&#xff1a; 通過選擇視頻來選擇你要標注的視頻&#xff0c;然后選擇保存路徑&#xff1a; 然后視頻兩個類別。當你看見視頻中的人…

springboot跨域問題 和 401

springboot跨域問題 和 401 1.跨域import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotatio…

構建直播平臺大體的流程

? 直播流程完整鏈路&#xff08;基于 SRS OBS 前后端&#xff09;&#x1f9cd;?♂? 用戶操作流程&#xff1a;? 用戶登錄系統&#xff08;前端&#xff09;系統中校驗用戶身份&#xff08;JWT 等&#xff09;后端可能校驗權限&#xff0c;比如“是否有開播資格”? 用戶…

KOSMOS-2: 將多模態大型語言模型與世界對接

溫馨提示&#xff1a; 本篇文章已同步至"AI專題精講" KOSMOS-2: 將多模態大型語言模型與世界對接 摘要 我們介紹了 KOSMOS-2&#xff0c;一種多模態大型語言模型&#xff08;MLLM&#xff09;&#xff0c;賦予了模型感知物體描述&#xff08;例如&#xff0c;邊界框…

協作機器人操作與編程-PE系統示教編程和腳本講解(直播回放)

協作機器人操作與編程-PE系統示教編程和腳本講解本次講解主要圍繞協作機器人PE系統的操作與編程展開&#xff0c;內容涵蓋軟件安裝、虛擬機配置、手動操作、在線編程、變量設置、網絡通信及標定方法等方面。以下是主要內容要點提煉&#xff1a; 軟件安裝與虛擬機配置 需從官網下…

【前后端】Node.js 模塊大全

用到的全部總結在這里&#xff0c;不定期更新 鏈接 node一本通 包括&#xff1a; express path fs/ process/ os/ http/ mysql/mongoose/ express-jwt/jsonwebtoken/ dotenv/ multer/ swagger/ cors/ nodemon (docker篇有)常用模塊 內置 fs 文件系統操作&#xff08;讀寫、重命…