Spring Boot 集成 Seata 解决分布式事务问题
seata 简介
Seata 是 阿里巴巴2019年开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里内部一直扮演着分布式一致性中间件的角色,帮助阿里度过历年的双11,对各业务进行了有力的支撑。经过多年沉淀与积累,2019.1 Seata 正式宣布对外开源 。目前 Seata 1.0 已经 GA。
微服务中的分布式事务问题
让我们想象一下传统的单片应用程序,它的业务由3个模块组成,他们使用单个本地数据源。自然,本地事务将保证数据的一致性。
微服务架构已发生了变化。上面提到的3个模块被设计为3种服务。本地事务自然可以保证每个服务中的数据一致性。但是整个业务逻辑范围如何?
Seata怎么办?
我们说,分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
Seata有3个基本组成部分:
- 事务协调器(TC):维护全局事务和分支事务的状态,驱动全局提交或回滚。
- 事务管理器TM:定义全局事务的范围:开始全局事务,提交或回滚全局事务。
- 资源管理器(RM):管理正在处理的分支事务的资源,与TC对话以注册分支事务并报告分支事务的状态,并驱动分支事务的提交或回滚。
Seata管理的分布式事务的典型生命周期:
- TM要求TC开始一项新的全局事务。TC生成代表全局事务的XID。
- XID通过微服务的调用链传播。
- RM将本地事务注册为XID到TC的相应全局事务的分支。
- TM要求TC提交或回退相应的XID全局事务。
- TC驱动XID的相应全局事务下的所有分支事务以完成分支提交或回滚。
快速开始
用例
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
环境准备
步骤 1:建立数据库
# db_seata DROP SCHEMA IF EXISTS db_seata; CREATE SCHEMA db_seata; USE db_seata; # Account CREATE TABLE `account_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` VARCHAR(255) DEFAULT NULL, `money` INT(11) DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; INSERT INTO account_tbl (id, user_id, money) VALUES (1, '1001', 10000); INSERT INTO account_tbl (id, user_id, money) VALUES (2, '1002', 10000); # Order CREATE TABLE `order_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` VARCHAR(255) DEFAULT NULL, `commodity_code` VARCHAR(255) DEFAULT NULL, `count` INT(11) DEFAULT '0', `money` INT(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; # Storage CREATE TABLE `storage_tbl` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `commodity_code` VARCHAR(255) DEFAULT NULL, `count` INT(11) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `commodity_code` (`commodity_code`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; INSERT INTO storage_tbl (id, commodity_code, count) VALUES (1, '2001', 1000); CREATE TABLE `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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
seata AT 模式需要 undo_log 表,另外三张是业务表。
步骤 2: 启动 Seata Server
Server端存储模式(store.mode)现有file、db两种(后续将引入raft),file模式无需改动,直接启动即可。db模式需要导入用于存储全局事务回话信息的三张表。
*注:file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;
db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些*
可以直接通过bash 脚本启动 Seata Server,也可以通过 Docker 镜像启动,但是 Docker 方式目前只支持使用 file 模式,不支持将 Seata-Server 注册到 Eureka 或 Nacos 等注册中心。
通过脚本启动
在 https://github.com/seata/seata/releases 下载相应版本的 Seata Server,解压后执行以下命令启动,这里使用 file 配置
通过 Docker 启动
docker run --name seata-server -p 8091:8091 seataio/seata-server:latest
项目介绍
项目名 | 地址 | 说明 |
---|---|---|
sbm-account-service | 127.0.0.1:8081 | 账户服务 |
sbm-order-service | 127.0.0.1:8082 | 订单服务 |
sbm-storage-service | 127.0.0.1:8083 | 仓储服务 |
sbm-business-service | 127.0.0.1:8084 | 主业务 |
seata-server | 172.16.2.101:8091 | seata-server |
核心代码
为了不让篇幅太长,这里只给出部分代码,详细代码文末会给出源码地址
maven 引入 seata 的依赖 eata-spring-boot-starter
<dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>
仓储服务
application.properties
spring.application.name=account-service server.port=8081 spring.datasource.url=jdbc:mysql://172.16.2.101:3306/db_seata?useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 seata.tx-service-group=my_test_tx_group mybatis.mapper-locations=classpath*:mapper/*Mapper.xml seata.service.grouplist=172.16.2.101:8091 logging.level.io.seata=info logging.level.io.seata.samples.account.persistence.AccountMapper=debug
StorageService
public interface StorageService { /** * 扣除存储数量 */ void deduct(String commodityCode, int count); }
订单服务
public interface OrderService { /** * 创建订单 */ Order create(String userId, String commodityCode, int orderCount); }
帐户服务
public interface AccountService { /** * 从用户账户中借出 */ void debit(String userId, int money); }
主要业务逻辑
只需要使用一个 @GlobalTransactional 注解在业务方法上。
@GlobalTransactional public void purchase(String userId, String commodityCode, int orderCount) { LOGGER.info("purchase begin ... xid: " + RootContext.getXID()); storageClient.deduct(commodityCode, orderCount); orderClient.create(userId, commodityCode, orderCount); }
XID 的传递
全局事务ID的跨服务传递,需要我们自己实现,这里通过拦截器的方式。每个服务都需要添加下面两个类。
SeataFilter
@Component public class SeataFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; String xid = req.getHeader(RootContext.KEY_XID.toLowerCase()); boolean isBind = false; if (StringUtils.isNotBlank(xid)) { RootContext.bind(xid); isBind = true; } try { filterChain.doFilter(servletRequest, servletResponse); } finally { if (isBind) { RootContext.unbind(); } } } @Override public void destroy() { } }
SeataRestTemplateAutoConfiguration
@Configuration public class SeataRestTemplateAutoConfiguration { @Autowired( required = false ) private Collection<RestTemplate> restTemplates; @Autowired private SeataRestTemplateInterceptor seataRestTemplateInterceptor; public SeataRestTemplateAutoConfiguration() { } @Bean public SeataRestTemplateInterceptor seataRestTemplateInterceptor() { return new SeataRestTemplateInterceptor(); } @PostConstruct public void init() { if (this.restTemplates != null) { Iterator var1 = this.restTemplates.iterator(); while (var1.hasNext()) { RestTemplate restTemplate = (RestTemplate) var1.next(); List<ClientHttpRequestInterceptor> interceptors = new ArrayList(restTemplate.getInterceptors()); interceptors.add(this.seataRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); } } } }
测试
测试成功场景:
curl -X POST http://127.0.0.1:8084/api/business/purchase/commit
此时返回结果为:true
测试失败场景:
UserId 为1002 的用户下单,sbm-account-service会抛出异常,事务会回滚
http://127.0.0.1:8084/api/business/purchase/rollback
此时返回结果为:false
查看 undo_log 的日志或者主键,可以看到在执行过程中有保存数据。
如查看主键自增的值,在执行前后的值会发生变化,在执行前是 1,执行后是 7 。
源码地址
https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-seata
参考
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
汇付天下与阿里云合作打造企业级移动中台,运营效率提升100%
随着移动互联网的普及,中国移动支付交易规模已超过百万亿元,居全球之首。数字化时代的兴起,促使各大企业都将移动化转型提升到战略高度。 作为国内领先的支付科技公司,2018年汇付天下计划打造企业级移动中台,统一运维、统一运营并统一移动研发规范,支撑业务不断变化及展需求,助力企业降本增效。 在建设移动中台能力时,汇付天下期望依托阿里云移动研发平台EMAS的全面能力,快速完成业务移动化的专项升级目标。提升App研发效率的同时,运用视觉设计和数据技术大幅提升App体验,采用个性化的数据分析,给用户提供极致的千人千面推荐服务。 2019年7月11日,经过30天的紧密建设,汇付天下正式宣布:EMAS平台正式投入运营。在短短2个月时间内,移动端产品的运营效率得到了大幅提升,各项营销活动的用户流量和活动打开率增长100%以上,支付成功率及所带来的的活动营收也成倍增长。 阿里云移动研发平台期望把阿里巴巴近十年在移动互联网行业沉淀的DevOps研发支撑能力、移动App基础中间件能力开放给客户,帮助传统企业快速完成业务移动化的转型升级目标。 未来汇付和阿里云EMAS会在数字化领域做更深入的探索和合作,将目光聚...
- 下一篇
麒麟 UKUI 3.0 发布预告视频,能否接替 Win 7 成为大家的新选择?
在 Win7 停服的第二天,麒麟团队随即发布了 UKUI3.0 的开始菜单预告视频。UKUI 是由麒麟团队开发的基于 Linux 发行版的轻量级桌面环境。UKUI 的设计紧贴普通用户需求,特别是针对有一定 Windows 系统使用习惯的用户,减少其使用 Linux 系统的学习成本。 全新 UKUI3.0 自 2019 年启动设计和研发工作,使用 Qt 语言进行开发,秉承“友好易用,简单轻松” 的设计理念,将为普通用户架起迈向 Linux 的桥梁。 视频预览:https://www.bilibili.com/video/av83527870/ 优麒麟开源桌面 Linux 操作系统创建于 2013 年,已累计发布 14 个版本,是 Ubuntu 的官方衍生版本之一,默认搭载 UKUI 轻量级桌面环境。2020 年 4 月,即将迎来优麒麟 20.04 LTS 版本的发布,届时将搭载全新 UKUI 3.0 预览版,敬请期待。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,CentOS8安装Elasticsearch6.8.6