当前位置: 首页 > news >正文

【Easylive】使用Seata解决分布式事务问题

【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版

1. 为什么@Transactional在跨服务调用时不生效?

技术本质(结合代码示例):
postComment方法中:

// 本地数据库操作(可被@Transactional管理)
videoCommentMapper.insert(comment); // 跨服务调用(不受@Transactional控制)
videoClient.updateCountInfo(comment.getVideoId(),...); 

问题根源:

  1. 事务隔离性:
    @Transactional只能管理当前服务的数据库连接
    videoClient.updateCountInfo()通过HTTP调用其他服务,属于独立事务
  2. 两阶段问题:
    • 阶段1:本地insert成功提交
    • 阶段2:远程调用失败时,本地事务无法自动回滚
  3. CAP理论限制:
    • 跨服务操作涉及网络分区容忍性,传统事务模型无法保证CP

生活化比喻:

就像你网购时:

  • 商家(服务A)确认发货(本地事务提交)
  • 但快递(服务B)丢件了(远程调用失败)
  • 没有平台(Seata)协调的话,钱货两失!

2. Seata解决方案全流程

第一步:启动Seata服务

  1. 下载Seata Server(1.6.1+)
  2. 配置注册中心(修改conf/registry.conf):
    registry {type = "nacos"nacos { serverAddr = "127.0.0.1:8848"namespace = "你的命名空间" # 可选}
    }
    
  3. 启动:bin/seata-server.sh -p 8091 -h 127.0.0.1

第二步:数据库准备
在每个业务库执行:

-- Seata核心表(用于事务协调)
CREATE TABLE IF NOT EXISTS `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- Seata Server需要的表(在独立数据库执行)
CREATE TABLE `global_table` (...);
CREATE TABLE `branch_table` (...);
CREATE TABLE `lock_table` (...);

第三步:项目配置

  1. 添加依赖(所有微服务):

    <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><version>2.2.6.RELEASE</version>
    </dependency>
    
  2. Nacos配置(以video-service为例):

    spring:cloud:alibaba:seata:tx-service-group: video-service-group # 与Seata Server配置一致seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848config:type: nacos
    

第四步:代码改造
postComment方法上:

@GlobalTransactional(rollbackFor = Exception.class,  // 所有异常都回滚timeoutMills = 60000,          // 超时时间(毫秒)name = "postCommentTx"         // 全局事务名(可查日志)
)
public void postComment(VideoComment comment, Integer replyCommentId) {// 原业务逻辑不变// Seata会自动拦截以下操作:// 1. videoCommentMapper.insert()// 2. videoClient.updateCountInfo()
}

关键机制:

  1. 事务ID传播:
    • Seata通过XID(全局事务ID)串联各服务
    • 自动通过Feign请求头传递seata_xid=123456
  2. 二阶段提交:
    第一阶段:准备阶段(Prepare)
    事务管理器 Seata服务端(TC) 服务A(RM) 服务B(RM) 1. 开启全局事务(XID=123) 2. 执行SQL并注册分支事务 3. 报告准备状态(就绪) 4. 调用远程服务并注册分支 5. 报告准备状态(就绪) 所有RM返回"就绪"后 进入第二阶段 事务管理器 Seata服务端(TC) 服务A(RM) 服务B(RM)

第二阶段:提交/回滚(Commit/Rollback)

事务管理器 Seata服务端(TC) 服务A(RM) 服务B(RM) RM1/RM2 6. 提交指令 7. 提交指令 8. 删除undo_log 6. 回滚指令 7. 回滚指令 8. 用undo_log恢复数据 alt [全部成功] [任一失败] 9. 最终状态通知 事务管理器 Seata服务端(TC) 服务A(RM) 服务B(RM) RM1/RM2
  1. 回滚原理:
    • 通过undo_log表中的回滚日志反向补偿
    • 例如:删除已插入的评论记录

  1. 验证与排查技巧
    验证步骤:
  2. 查看Seata控制台:
    • 访问http://127.0.0.1:7091
    • 检查事务列表是否包含postCommentTx
  3. 强制触发异常:
    // 在updateCountInfo()中模拟失败
    if (Math.random() > 0.5) {throw new RuntimeException("模拟远程调用失败");
    }
    
    • 观察本地video_comment表是否回滚

常见问题解决:

问题现象可能原因解决方案
No available service for cluster事务组名不匹配检查tx-service-group一致性
Could not register branch数据库未建undo_log执行建表SQL
回滚失效方法内捕获异常确保异常抛出到@GlobalTransactional

生活化总结

Seata就像跨国贸易的支付宝:

  1. 买家付款(本地事务)→ 资金暂存平台(Phase1)
  2. 卖家发货(远程调用)→ 平台监控物流(Phase1)
  3. 确认收货后双方结算(Phase2 Commit)
  4. 如果卖家不发货,平台退款(Phase2 Rollback)

通过这套机制,postComment方法就像有了一个全自动保险,无论评论数据保存和计数更新相隔多远,都能保证要么全部成功,要么全部回滚。


代码
使用@GlobalTransactional注解

@Override
@GlobalTransactional(name = "postCommentTx", rollbackFor = Exception.class)
public void postComment(VideoComment comment, Integer replyCommentId) {// 1. 获取视频信息VideoInfo videoInfo = videoClient.getVideoInfoByVideoId(comment.getVideoId());if (videoInfo == null) {throw new BusinessException(ResponseCodeEnum.CODE_600);}// 2. 检查评论是否关闭if (videoInfo.getInteraction() != null && videoInfo.getInteraction().contains(Constants.ZERO.toString())) {throw new BusinessException("UP主已关闭评论区");}// 3. 处理回复评论逻辑if (replyCommentId != null) {VideoComment replyComment = getVideoCommentByCommentId(replyCommentId);if (replyComment == null || !replyComment.getVideoId().equals(comment.getVideoId())) {throw new BusinessException(ResponseCodeEnum.CODE_600);}if (replyComment.getpCommentId() == 0) {comment.setpCommentId(replyComment.getCommentId());} else {comment.setpCommentId(replyComment.getpCommentId());comment.setReplyUserId(replyComment.getUserId());}UserInfo userInfo = videoClient.getUserInfoByUserId(replyComment.getUserId());comment.setReplyNickName(userInfo.getNickName());comment.setReplyAvatar(userInfo.getAvatar());} else {comment.setpCommentId(0);}// 4. 设置评论信息comment.setPostTime(new Date());comment.setVideoUserId(videoInfo.getUserId());// 5. 插入评论this.videoCommentMapper.insert(comment);// 6. 更新评论计数if (comment.getpCommentId() == 0) {this.videoClient.updateCountInfo(comment.getVideoId(), UserActionTypeEnum.VIDEO_COMMENT.getField(), 1);}
}

相关文章:

  • 华为云获取IAM用户Token的方式及适用分析
  • 阿里云入门手册
  • 富文本编辑器
  • SSH反向代理
  • go语言的八股文
  • 突破传统!SEARCH-R1如何让LLM与搜索引擎协同推理?
  • 音视频学习(三十五):aud
  • DeepSeek 大模型 + LlamaIndex + MySQL 数据库 + 知识文档 实现简单 RAG 系统
  • from tensorflow.keras.models import Model中Model报红;以及动态链接库(DLL)初始化例程失败
  • TensorFlow和PyTorch学习原理解析
  • 创新项目实训开发日志3
  • 维度建模工具箱 提纲与总结
  • Spring如何通过XML注册Bean
  • Ldap高效数据同步- Delta-Syncrepl复制模式配置实战手册(上)
  • 第 4 篇:平稳性 - 时间序列分析的基石
  • Github 热点项目 Jumpserver开源堡垒机让服务器管理效率翻倍
  • 前端笔记-Axios
  • 云原生与AI的关系是怎么样的?
  • Unreal Engine中FRotator与FQuat在赛车游戏方向盘控制中的协同应用解析
  • Android Kotlin+Compose首个应用
  • 广发基金刘格崧一季报:首次买入广东宏大、分众传媒,减仓亿纬锂能
  • 夜读丨一位医生0点后的朋友圈
  • 上海黄金交易所:贵金属价格波动剧烈,提示投资者做好风险防范
  • 解放日报:订单不撤,中国工程师有能力
  • 北理工:开除宫某党籍,免去行政职务,解除聘用关系
  • 95后男中音胡斯豪敲开芝加哥抒情歌剧院大门