RabbitMQ和Seata沖突嗎?Seata與Spring中的事務管理沖突嗎

1. @GlobalTransactional@Transactional 是否沖突?

答:不沖突,它們可以協同工作,但作用域不同。

  • @Transactional: 這是 Spring 提供的注解,用于管理單個數據源內的本地事務。在你當前的 register 方法中,它確保了 userRepository.save(user) 操作要么成功提交到 auth_service_new 的數據庫 (mall_auth_new),要么在發生異常時回滾(比如數據庫連接失敗、約束沖突等)。它只關心本服務內的數據庫操作原子性。
  • @GlobalTransactional: 這是 Seata 提供的注解,用于開啟一個分布式全局事務。它的目的是協調跨多個服務、多個數據源的操作,保證這些操作要么全部成功,要么全部回滾。Seata 的 AT 模式(你目前可能使用的模式,因其最簡單)通過代理數據源 (DataSourceProxy) 自動記錄 SQL 執行前后的鏡像,并在需要時生成反向 SQL 來實現回滾。

如何協同? Seata AT 模式下的分支事務實際上是基于本地事務的。當 @GlobalTransactional 存在時,Seata 會攔截 @Transactional 管理的本地事務的提交/回滾。

  • 本地事務提交時:Seata RM (Resource Manager) 會先向 TC (Transaction Coordinator) 注冊分支,報告本地事務執行成功(Phase 1),但不會立即真正提交物理數據庫連接。它會等待 TC 的統一指令。
  • 全局事務提交時:TC 通知所有 RM 提交分支事務(Phase 2),這時 RM 才提交本地事務對應的物理數據庫連接。
  • 全局事務回滾時:TC 通知所有 RM 回滾分支事務(Phase 2),RM 會根據之前記錄的 Undo Log 生成反向 SQL 來回滾本地數據庫的更改。

結論:同時使用兩者是常見且必要的。@Transactional 保證本地操作的原子性,而 @GlobalTransactional 則將這種原子性擴展到分布式環境下的多個參與者。

2. Seata (AT 模式) 和 RabbitMQ 是否沖突?

答:沖突!在期望跨服務數據庫原子性的場景下,同步調用 Seata AT 模式和異步發送 RabbitMQ 消息是矛盾的。

  • Seata AT 模式的局限性: Seata AT 模式主要設計用于同步調用場景下的數據庫操作。它無法管理消息隊列(如 RabbitMQ)的操作。也就是說,Seata 不能

    • 保證消息發送成功后,如果后續全局事務需要回滾,能把消息“撤回”。
    • 保證消息被消費者成功處理后,如果全局事務需要回滾,能讓消費者的操作也回滾。
  • 你當前代碼的問題:

    1. AuthServiceImpl.register 方法在 @GlobalTransactional 內執行 userRepository.save(user)。這個操作被 Seata 納入了全局事務分支。
    2. 緊接著,它調用 messageService.sendUserCreatedEvent(savedUser) 發送 RabbitMQ 消息。這個發送操作本身不受 Seata 全局事務的管理
    3. user_moudle 中的 UserEventListener異步地消費這個消息,并執行 userService.createUserFromEvent 來寫入 user_moudle 的數據庫 (mall_users)。這個數據庫寫入操作也不在 AuthServiceImpl.register 發起的那個 Seata 全局事務的范圍內。
  • 后果:

    • 如果在發送消息之后AuthServiceImpl.register 方法內部(或其調用的其他同步下游服務)發生了需要全局回滾的異常,auth_service_new 數據庫的 User 記錄會被 Seata 回滾,但 RabbitMQ 消息已經發出去了user_moudle 仍然會收到消息并嘗試創建用戶,導致數據不一致(user_moudle 有用戶,auth_service_new 沒有)。
    • 如果在 user_moudle 消費消息并寫入數據庫時失敗auth_service_new 的事務早已提交(因為消息是異步的),也無法回滾,同樣導致數據不一致。

結論:如果你希望 auth_service_new 寫入 auth_user 表 和 user_moudle 寫入 ums_user 表這兩個數據庫操作具有原子性(要么都成功,要么都失敗),那么在 @GlobalTransactional 方法內部使用 RabbitMQ 進行跨服務通信是錯誤的設計。

3. 應該怎么做?

為了實現 auth_service_newuser_moudle 在用戶注冊時的數據庫寫入原子性,最佳實踐是使用同步調用,讓 user_moudle 的數據庫操作也成為 Seata 全局事務的一個分支。

修改步驟:

  1. auth_service_new 中定義 Feign 客戶端調用 user_moudle:

    • 創建一個接口,例如 UserModuleClient.java:
    package com.mall.auth.client;import com.mall.auth.dto.UserSyncDTO; // 需要創建一個簡單的DTO傳遞必要信息
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;// name 指向 user_moudle 的服務名 (spring.application.name)
    @FeignClient(name = "userservice", path = "/api/internal/users") // 使用內部API路徑
    public interface UserModuleClient {@PostMapping("/sync-create")void syncCreateUser(@RequestBody UserSyncDTO userSyncDTO);
    }
    
    • 創建 UserSyncDTO.java (可以簡化 RegisterRequestUserCreatedEvent 的字段):
    package com.mall.auth.dto;import lombok.Builder;
    import lombok.Data;@Data
    @Builder
    public class UserSyncDTO {private Long authUserId;private String username;private String email;private String phone;// 注意:不需要傳遞密碼,user_moudle 只存占位符
    }
    
  2. 修改 AuthServiceImpl.register 方法:

    • 移除 messageService.sendUserCreatedEvent 調用。
    • 注入并使用 UserModuleClient 進行同步調用。
    // ... 其他注入 ...
    import com.mall.auth.client.UserModuleClient;
    import com.mall.auth.dto.UserSyncDTO;
    // ...@Service
    public class AuthServiceImpl implements AuthService {// ... 其他字段和構造函數 ...private final UserModuleClient userModuleClient;public AuthServiceImpl(// ... 其他參數 ...UserModuleClient userModuleClient, // 添加注入MessageService messageService) { // MessageService 仍然可以注入,但注冊時不在此調用// ... 其他賦值 ...this.userModuleClient = userModuleClient;this.messageService = messageService; // 保留注入}@GlobalTransactional(name = "user-register-tx", rollbackFor = Exception.class)@Override@Transactional // 本地事務仍然需要public User register(RegisterRequest registerRequest) {log.info("開始用戶注冊流程 (同步事務): {}", registerRequest.getUsername());// ... (省略之前的檢查邏輯) ...// 創建新用戶User user = User.builder()// ... (省略屬性設置) ....build();// 1. 保存到 auth_service_new 數據庫 (參與 Seata 分支事務)User savedUser = userRepository.save(user);log.info("AuthService: 用戶基礎信息保存成功: {}", savedUser.getUsername());// 2. 同步調用 user_moudle 保存用戶信息 (參與 Seata 分支事務)try {UserSyncDTO syncDTO = UserSyncDTO.builder().authUserId(savedUser.getId()).username(savedUser.getUsername()).email(savedUser.getEmail()).phone(savedUser.getPhone()).build();log.info("AuthService: 準備同步調用 UserModule 創建用戶...");userModuleClient.syncCreateUser(syncDTO); // 通過 Feign 調用log.info("AuthService: UserModule 同步調用成功");} catch (Exception e) {log.error("AuthService: 同步調用 UserModule 失敗: {}", e.getMessage(), e);// 拋出異常,觸發 @GlobalTransactional 回滾// 注意:需要確保 Feign 客戶端在調用失敗時能正確拋出異常被 Seata 捕獲// 可能需要配置 Feign 的 ErrorDecoderthrow new RuntimeException("同步用戶模塊失敗,觸發全局回滾", e);}// 發送消息的操作可以移到事務成功提交之后 (如果還需要的話)// 例如使用 Spring 的 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)// messageService.sendUserCreatedEvent(savedUser); // 從事務中移除return savedUser;}// ... 其他方法 ...
    }
    
  3. user_moudle 中添加對應的 Controller Endpoint:

    • 創建一個新的 Controller 或在 UserController 中添加一個內部接口(路徑建議與 Feign Client 對應,如 /api/internal/users)。
    package com.user.controler;import com.user.dto.UserSyncDTO; // 引入對應的 DTO
    import com.user.service.UserService;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;@Slf4j
    @RestController
    @RequestMapping("/api/internal/users") // 內部調用路徑
    @RequiredArgsConstructor
    public class UserInternalController {private final UserService userService;@PostMapping("/sync-create")public ResponseEntity<Void> syncCreateUser(@RequestBody UserSyncDTO userSyncDTO) {log.info("UserModule: 收到同步創建用戶請求: authUserId={}", userSyncDTO.getAuthUserId());try {// 這里需要一個類似 createUserFromEvent 的方法,但參數是 UserSyncDTO// 或者直接調用現有的 createUserFromEvent,但需要適配 DTOuserService.createUserFromSync(userSyncDTO); // 假設有這個方法log.info("UserModule: 同步創建用戶成功: authUserId={}", userSyncDTO.getAuthUserId());return ResponseEntity.ok().build();} catch (Exception e) {log.error("UserModule: 同步創建用戶失敗: {}", e.getMessage(), e);// 拋出異常,讓 Seata 感知到錯誤,觸發全局回滾// Spring MVC 默認會將未捕獲的異常轉換為 500 錯誤,// Feign 客戶端默認會將 4xx/5xx 視為異常throw new RuntimeException("創建用戶記錄失敗", e);}}
    }
    
    • 確保 user_moudleUserService 有一個處理 UserSyncDTO 的方法,并且這個方法的數據庫操作會因為配置了 DataSourceProxy 而自動加入到 Seata 的全局事務中。

總結:

  • @GlobalTransactional@Transactional 不沖突,前者依賴后者。
  • 在期望數據庫原子性的場景下,Seata AT 與異步消息(RabbitMQ)沖突
  • 最佳實踐:將跨服務需要原子性的數據庫操作改為同步 RPC 調用 (如 Feign),并在調用發起方的方法上使用 @GlobalTransactional。移除事務內的異步消息發送。

這樣修改后,用戶注冊時對兩個數據庫的寫入就能真正實現分布式事務的原子性了。

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

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

相關文章

一臺服務器已經有個python3.11版本了,如何手動安裝 Python 3.10,兩個版本共存

環境&#xff1a; debian12.8 python3.11 python3.10 問題描述&#xff1a; 一臺服務器已經有個python3.11版本了&#xff0c;如何手動安裝 Python 3.10&#xff0c;兩個版本共存 解決方案&#xff1a; 1.下載 Python 3.10 源碼&#xff1a; wget https://www.python.or…

c++中的enum變量 和 constexpr說明符

author: hjjdebug date: 2025年 04月 23日 星期三 13:40:21 CST description: c中的enum變量 和 constexpr說明符 文章目錄 1.Q:enum 類型變量可以有,--操作嗎&#xff1f;1.1補充: c/c中enum的另一個細微差別. 2.Q: constexpr 修飾的函數,要求傳入的參數必需是常量嗎&#xff…

postman工具

postman工具 進入postman官網 www.postman.com/downloads/ https://www.postman.com/downloads/ https://www.postman.com/postman/published-postman-templates/documentation/ae2ja6x/postman-echo?ctxdocumentation Postman Echo is a service you can use to test your …

Spring和Spring Boot集成MyBatis的完整對比示例,包含從項目創建到測試的全流程代碼

以下是Spring和Spring Boot集成MyBatis的完整對比示例&#xff0c;包含從項目創建到測試的全流程代碼&#xff1a; 一、Spring集成MyBatis示例 1. 項目結構 spring-mybatis-demo/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com.example/…

【數據可視化-24】巧克力銷售數據的多維度可視化分析

?? 博主簡介:曾任某智慧城市類企業算法總監,目前在美國市場的物流公司從事高級算法工程師一職,深耕人工智能領域,精通python數據挖掘、可視化、機器學習等,發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN人工智能領域的優質創作者,提供AI相關的技術咨詢、項目開發和個…

c語言-分支結構

以下是我初學C語言的筆記記錄&#xff0c;歡迎留言補充 一&#xff0c;分支結構分為幾個 兩個&#xff0c;一個是if語句&#xff0c;一個是Switch語句 二&#xff0c;if語句 &#xff08;1&#xff09;結構體 int main() {if()//判斷條件{//表達式}else if()//判斷條件{//表達式…

數據庫MySQL學習——day4(更多查詢操作與更新數據)

文章目錄 1、聚合函數&#xff08;Aggregate Functions&#xff09;2、分組查詢&#xff08;GROUP BY&#xff09;3、更新數據&#xff08;UPDATE&#xff09;4、刪除數據&#xff08;DELETE&#xff09;5、進階練習示例6、 今日小結 1、聚合函數&#xff08;Aggregate Functio…

Spark-SQL 項目

一、項目概述 &#xff08;一&#xff09;實驗目標 統計有效數據條數&#xff1a;篩選出uid、phone、addr三個字段均無空值的記錄并計數。提取用戶數量最多的前 20 個地址&#xff1a;按地址分組統計用戶數&#xff0c;按降序排序后取前 20 名。 &#xff08;二&#xff09;…

Redis的ZSet對象底層原理——跳表

我們來聊聊「跳表&#xff08;Skip List&#xff09;」&#xff0c;這是一個既經典又優雅的數據結構&#xff0c;尤其在 Redis 中非常重要&#xff0c;比如 ZSet&#xff08;有序集合&#xff09;底層就用到了跳表。 &#x1f31f; 跳表&#xff08;Skip List&#xff09;簡介 …

2025深圳中興通訊安卓開發社招面經

2月27號 中興通訊一面 30多分鐘 自我介紹 聊項目 我的優缺點&#xff0c;跟同事相比&#xff0c;有什么突出的地方 Handler機制&#xff0c;如何判斷是哪個消息比較耗時 設計模式&#xff1a;模板模式 線程的狀態 線程的開啟方式 線程池原理 活動的啟動模式 Service和Activity…

【Castle-X機器人】二、智能導覽模塊安裝與調試

持續更新。。。。。。。。。。。。。。。 【Castle-X機器人】智能導覽模塊安裝與調試 二、智能導覽模塊安裝與調試2.1 智能導覽模塊安裝2.2 智能導覽模塊調試2.2.1 紅外測溫傳感器測試2.2.2 2D攝像頭測試 二、智能導覽模塊安裝與調試 2.1 智能導覽模塊安裝 使用相應工具將智能…

深入理解二叉樹遍歷:遞歸與棧的雙重視角

二叉樹的遍歷前序遍歷中序遍歷后續遍歷總結 二叉樹的遍歷 雖然用遞歸的方法遍歷二叉樹實現起來更簡單&#xff0c;但是要想深入理解二叉樹的遍歷&#xff0c;我們還必須要掌握用棧遍歷二叉樹&#xff0c;遞歸其實就是利用了系統棧去遍歷。特此記錄一下如何用雙重視角去看待二叉…

Qt Creator中自定義應用程序的可執行文件圖標

要在Qt Creator中為你的應用程序設置自定義可執行文件圖標&#xff0c;你需要按照以下步驟操作&#xff1a; Windows平臺設置方法 準備圖標文件&#xff1a; 創建一個.ico格式的圖標文件&#xff08;推薦使用256x256像素&#xff0c;包含多種尺寸&#xff09; 可以使用在線工…

Windows11系統中GIT下載

Windows11系統中GIT下載 0、GIT背景介紹0.0 GIT概述0.1 GIT誕生背景0.2 Linus Torvalds 的設計目標0.3 Git 的誕生&#xff08;2005 年&#xff09;0.4 Git 的后續發展0.5 為什么 Git 能成功&#xff1f; 1、資源下載地址1.1 官網資源1.2 站內資源 2、安裝指導3、驗證是否下載完…

react的fiber 用法

在 React 里&#xff0c;Fiber 是 React 16.x 及后續版本采用的協調算法&#xff0c;它把渲染工作分割成多個小任務&#xff0c;讓 React 可以在渲染過程中暫停、恢復和復用任務&#xff0c;以此提升渲染性能與響應能力。在實際開發中&#xff0c;你無需直接操作 Fiber 節點&am…

FPGA前瞻篇-數字電路基礎-邏輯門電路設計

模擬信號&#xff1a; 一條隨時間連續變化、平滑波動的曲線&#xff0c;比如正弦波。 數字信號&#xff1a; 一條只有高低兩個狀態&#xff08;0和1&#xff09;&#xff0c;跳變清晰的方波曲線。 在 IC 或 FPGA 的邏輯設計中&#xff0c;我們通常只能處理數字信號&#xff0…

RabbitMQ 基礎概念(隊列、交換機、路由鍵、綁定鍵、信道、連接、虛擬主機、多租戶)介紹

本文是博主在梳理 RabbitMQ 知識的過程中&#xff0c;將所遇到和可能會遇到的基礎知識記錄下來&#xff0c;用作梳理 RabbitMQ 的整體架構和功能的線索文章&#xff0c;通過查找對應的知識能夠快速的了解對應的知識而解決相應的問題。 文章目錄 一、RabbitMQ 是什么&#xff1f…

機器學習第一篇 線性回歸

數據集&#xff1a;公開的World Happiness Report | Kaggle中的happiness dataset2017. 目標&#xff1a;基于GDP值預測幸福指數。&#xff08;單特征預測&#xff09; 代碼&#xff1a; 文件一&#xff1a;prepare_for_traning.py """用于科學計算的一個庫…

Java面試高頻問題(29-30)

二十九、全鏈路壓測&#xff1a;數據隔離與流量 關鍵技術點 1. 流量染色&#xff1a;通過Header注入X-Test-TraceId標識壓測流量 2. 影子庫表&#xff1a;通過ShardingSphere實現數據隔離 3. 熔斷降級&#xff1a;壓測流量觸發異常時自動切換回生產數據源 數據隔離方案對比 …

Python常用的第三方模塊之數據分析【pdfplumber庫、Numpy庫、Pandas庫、Matplotlib庫】

【pdfplumber庫】從PDF文件中讀取內容 import pdfplumber #打開PDF文件 with pdfplumber.open(DeepSeek從入門到精通(20250204).pdf) as pdf:for i in pdf.pages: #遍歷頁print(i.extract_text()) #extract_text()方法提取內容print(f----------------第{i.page_number}頁結束…