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

【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);}
}
http://www.xdnf.cn/news/952.html

相关文章:

  • Android 中实现 GIF 图片动画
  • three.js中的instancedMesh类优化渲染多个同网格材质的模型
  • 《AI大模型应知应会100篇》第31篇:大模型重塑教育:从智能助教到学习革命的实践探索
  • 【大数据、数据开发与数据分析面试题汇总(含答案)】
  • langchain +ollama +chroma+embedding模型实现RAG入门级Demo(python版)
  • 量化交易 - RSRS(阻力支撑相对强度)- 正确用法 - 年均收益18%
  • EMQX安装使用和客户端认证
  • Kubernetes 节点摘除指南
  • LintCode第107题-单词拆分
  • 全排列问题cpp
  • Discuz论坛网站忘记管理员密码进不去管理中心怎么办?怎么改管理员密码?
  • stc32单片机实现串口2M波特率满带宽传输
  • C#接口开发异常:System.Web.HttpRequestValidationException
  • Linux421用户、组
  • qt画一朵花
  • ​001-内网穿透工具
  • 20250421在荣品的PRO-RK3566开发板的Android13下使用io命令控制GPIO
  • ArcGIS、ArcMap查看.shp文件时属性表中文乱码
  • 软件工程师中级考试-上午知识点总结(下)
  • Linux内核开发常用函数
  • Git创建空分支并推送到远程仓库
  • 大模型中超参数TopK是什么
  • 密码明文放在请求体是否有安全隐患?
  • 前端实战-AJAX
  • Spark(19)Yarn-tool接口
  • 力扣热题100——矩阵
  • 安卓的桌面 launcher是什么
  • 【AI】SpringAI 第三弹:接入通用大模型平台
  • CSS字体
  • 什么是SPA,SPA与MAP区别