SpringBoot+Dubbo+Seata分布式事务实战详解

前言

Seata 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。

事实上,官方在GitHub已经给出了多种环境下的Seata应用示例项目,地址:https://github.com/seata/seata-samples

为什么笔者要重新写一遍呢,主要原因有两点:

  1. 官网代码示例中,依赖太多,分不清哪些有什么作用
  2. Seata相关资料较少,笔者在搭建的过程中,遇到了一些坑,记录一下

一、环境准备

本文涉及软件环境如下:

  • SpringBoot 2.1.6.RELEASE
  • Dubbo 2.7.1
  • Mybatis 3.5.1
  • Seata 0.6.1
  • Zookeeper 3.4.10

1、业务场景

为了简化流程,我们只需要订单和库存两个服务。创建订单的时候,调用库存服务,扣减库存。

涉及的表设计如下:

CREATE TABLE `t_order` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `order_no` varchar(255) DEFAULT NULL,
 `user_id` varchar(255) DEFAULT NULL,
 `commodity_code` varchar(255) DEFAULT NULL,
 `count` int(11) DEFAULT '0',
 `amount` double(14,2) DEFAULT '0.00',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8;

CREATE TABLE `t_storage` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `commodity_code` varchar(255) DEFAULT NULL,
 `name` varchar(255) DEFAULT NULL,
 `count` int(11) DEFAULT '0',
 PRIMARY KEY (`id`),
 UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
另外还需要一个回滚日志表:
CREATE TABLE `undo_log` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `branch_id` bigint(20) NOT NULL,
 `xid` varchar(100) NOT NULL,
 `rollback_info` longblob NOT NULL,
 `log_status` int(11) NOT NULL,
 `log_created` datetime NOT NULL,
 `log_modified` datetime NOT NULL,
 `ext` varchar(100) DEFAULT NULL,
 `context` varchar(100) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8;

2、Seata下载安装

打开https://github.com/seata/seata/releases,目前最新版本是v0.6.1。

下载解压后,到seata-server-0.6.1\distribution\bin目录下可以看到seata-server.bat和seata-server.sh,选择一个双击执行。
不出意外的话,当你看到-Server started ...等字样,就正常启动了。

3、Maven依赖

由于是Dubbo项目,我们先引入Dubbo相关依赖。

<dependency>
 <groupId>org.apache.dubbo</groupId>
 <artifactId>dubbo</artifactId>
 <version>2.7.1</version>
</dependency>
<dependency>
 <groupId>org.apache.dubbo</groupId>
 <artifactId>dubbo-spring-boot-starter</artifactId>
 <version>2.7.1</version>
</dependency>
Dubbo的服务要注册到Zookeeper,引入curator客户端。
<dependency>
 <groupId>org.apache.curator</groupId>
 <artifactId>curator-framework</artifactId>
 <version>2.13.0</version>
</dependency>
<dependency>
 <groupId>org.apache.curator</groupId>
 <artifactId>curator-recipes</artifactId>
 <version>2.13.0</version>
</dependency>

最后,引入Seata。

<dependency>
 <groupId>io.seata</groupId>
 <artifactId>seata-all</artifactId>
 <version>0.6.1</version>
</dependency>

当然了,还有其他的如Mybatis、mysql-connector等就不粘了,自行引入即可。

二、项目配置

1、application.properties

这里只需要配置数据库连接信息和Dubbo相关信息即可。

server.port=8011

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata
spring.datasource.username=root
spring.datasource.password=root

dubbo.application.name=order-service
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
dubbo.consumer.timeout=9999999
dubbo.consumer.check=false

2、数据源

Seata 是通过代理数据源实现事务分支,所以需要先配置一个数据源的代理,否则事务不会回滚。

@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
 return new DataSourceProxy(dataSource);
}

注意,这里的DataSourceProxy类位于io.seata.rm.datasource包内。

3、Seata配置

还需要配置全局事务扫描器。有两个参数,一个是应用名称,一个是事务分组。

@Bean
public GlobalTransactionScanner globalTransactionScanner() {
 return new GlobalTransactionScanner("springboot-order", "my_test_tx_group");
}

事实上,关于Seata事务的一系列初始化工作都在这里完成。

4、配置注册中心

Seata连接到服务器的时候需要一些配置项,这时候有一个registry.conf文件可以指定注册中心和配置文件是什么。

这里有很多可选性,比如file、nacos 、apollo、zk、consul。

后面4个都是业界成熟的配置注册中心产品,为啥还有个file呢?

官方的初衷是在不依赖第三方配置注册中心的基础上快速集成测试seata功能,但是file类型本身不具备注册中心的动态发现和动态配置功能。

registry.conf文件内容如下:

registry {
 type = "file"
 file {
  name = "file.conf"
 }
}
config {
 # file、nacos 、apollo、zk、consul
 type = "file"
 file {
  name = "file.conf"
 }
}

如果你选择了file类型,通过name属性指定了file.conf,这个文件中指定了客户端或服务器的配置信息。比如传输协议、服务器地址等。

service {
 #vgroup->rgroup
 vgroup_mapping.my_test_tx_group = "default"
 #only support single node
 default.grouplist = "127.0.0.1:8091"
 #degrade current not support
 enableDegrade = false
 #disable
 disable = false
}

三、业务代码

1、库存服务

在库存服务中,拿到商品编码和购买总个数,扣减即可。

<update id="decreaseStorage">
 update t_storage set count = count-${count} where commodity_code = #{commodityCode}
</update>

然后用Dubbo将库存服务扣减接口暴露出去。

2、订单服务

在订单服务中,先扣减库存,再创建订单。最后抛出异常,然后去数据库检查事务是否回滚。

@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {

 System.out.println("开始全局事务。XID="+RootContext.getXID());
 StorageDTO storageDTO = new StorageDTO();
 storageDTO.setCount(orderDTO.getCount());
 storageDTO.setCommodityCode(orderDTO.getCommodityCode());

 //1、扣减库存
 storageDubboService.decreaseStorage(storageDTO);

 //2、创建订单
 orderDTO.setId(order_id.incrementAndGet());
 orderDTO.setOrderNo(UUID.randomUUID().toString());
 Order order = new Order();
 BeanUtils.copyProperties(orderDTO,order);
 orderMapper.createOrder(order);

 throw new RuntimeException("分布式事务异常..."+orderDTO.getOrderNo());
}

值得注意的是,在订单服务事务开始的方法上,需要标注@GlobalTransactional。另外,在库存服务的方法里,不需要此注解,事务会通过Dubbo进行传播。

四、注意事项

1、数据源

请切记,Seata 是通过代理数据源实现事务分支,一定不要忘记配置数据源代理。

2、主键自增

在数据库中,表里的主键ID字段都是自增的。如果你的字段不是自增的,那么在Mybatis的insert SQL中,要将列名写完整。
比如我们可以这样写SQL:

INSERT INTO table_name VALUES (值1, 值2,....)

那么这时候就要写成:

INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)

3、序列化问题

在订单表中,amount字段类型为double。在seata0.6.1版本中,默认的序列化方式为fastjson,但它会将这个字段序列化成bigdecimal类型,会导致后面类型不匹配。

但是在后续的seata0.7.0版本中(还未发布),已经将默认的序列化方式改为了jackson。

不过无需担心,这个问题一般不会出现。笔者是因为引错了一个包,才导致发现这问题。

4、本文代码

本文示例代码在:https://github.com/taoxun/springboot-dubbo-zookeeper-seata

5、其他

欢迎有问题及时交流~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Springboot-dubbo-fescar 阿里分布式事务的实现方法

    大家可以自行百度下阿里分布式事务,在这里我就不啰嗦了.下面是阿里分布式事务开源框架的一些资料,本文是springboot+dubbo+fescar的集成. 快速开始 https://github.com/alibaba/fescar/wiki/Quick-Start GIT地址 https://github.com/alibaba/fescar 1.sql CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `br

  • 详解Spring Boot微服务如何集成fescar解决分布式事务问题

    什么是fescar? 关于fescar的详细介绍,请参阅fescar wiki. 传统的2PC提交协议,会持有一个全局性的锁,所有局部事务预提交成功后一起提交,或有一个局部事务预提交失败后一起回滚,最后释放全局锁.锁持有的时间较长,会对并发造成较大的影响,死锁的风险也较高. fescar的创新之处在于,每个局部事务执行完立即提交,释放本地锁:它会去解析你代码中的sql,从数据库中获得事务提交前的事务资源即数据,存放到undo_log中,全局事务协调器在回滚的时候直接使用undo_log中的数据覆

  • SpringBoot+Dubbo+Seata分布式事务实战详解

    前言 Seata 是 阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题. 事实上,官方在GitHub已经给出了多种环境下的Seata应用示例项目,地址:https://github.com/seata/seata-samples. 为什么笔者要重新写一遍呢,主要原因有两点: 官网代码示例中,依赖太多,分不清哪些有什么作用 Seata相关资料较少,笔者在搭建的过程中,遇到了一些坑,记录一下 一.环境准备 本文涉及软件环境如下: SpringBoot

  • LCN分布式事务解决方案详解

    目录 一.什么是分布式事务? 二.lcn的实现思路 2.1 本地执行的状态怎么提交给全局事务? 2.2 本地事务的提交或回滚怎么实现? 三.lcn的使用 3.1 下载lcn-manager (全局的事务管理器) 3.2 配置lcn-manager 3.3 启动lcn 3.4 模拟转账服务 3.4.1 add-service 3.4.2 decr-service 3.5 2 个微服务都需要添加依赖 3.6 需要自定义数据库的连接池 3.7 使用 3.7.1 事务的发起者 3.7.2 添加配置文件

  • SpringCloud微服务开发基于RocketMQ实现分布式事务管理详解

    目录 消息队列实现分布式事务原理 RocketMQ的事务消息 代码实现 基础配置 发送半消息 执行本地事务与回查 Account-Service消费消息 测试 小结 消息队列实现分布式事务原理 首先让我们来看一下基于消息队列实现分布式事务的原理方案. 柔性事务 发送消息的服务有个OUTBOX数据表,在进行INSERT.UPDATE.DELETE 业务操作时也会给OUTBOX数据表INSERT一条消息记录,这样可以保证原子性,因为这是基于本地的ACID事务. OUTBOX表充当临时消息队列,然后我

  • springboot+dubbo+zookeeper的简单实例详解

    目录 服务端 消费端 在dubbo-admin-0.0.1-SNAPSHOT.jar的路径下启动dubbo,java -jar dubbo-admin-0.0.1-SNAPSHOT.jar dubbo-admin-0.0.1-SNAPSHOT.jar的包需要提前打好 启动zookeeper的zkServer.cmd和zkCli.cmd 需要的依赖 <!-- 导入依赖:zookeeper + dubbo --> <!-- https://mvnrepository.com/artifact

  • IntelliJ Idea SpringBoot 数据库增删改查实例详解

    SpringBoot 是 SpringMVC 的升级,对于编码.配置.部署和监控,更加简单 微服务 微服务是一个新兴的软件架构,就是把一个大型的单个应用程序和服务拆分为数十个的支持微服务.一个微服务的策略可以让工作变得更为简便,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议. Spring 为 微服务提供了一整套的组件-SpringClound , SpirngBoot 就是该基础. 第一个SpringBoot程序 这里使用的开发软件是IntelliJ Idea,和Eclipse

  • Spring Boot事务配置详解

    1.在启动主类添加注解:@EnableTransactionManagement 来启用注解式事务管理,相当于之前在xml中配置的<tx:annotation-driven />注解驱动. 2.在需要事务的类或者方法上面添加@Transactional() 注解,里面可以配置需要的粒度: 这么多东西提供配置: Isolation :隔离级别 隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取.重复读.幻读. 我们可以看 org.springframework.

  • SpringBoot之使用枚举参数案例详解

    接口开发过程中不免有表示类型的参数,比如 0 表示未知,1 表示男,2 表示女.通常有两种做法,一种是用数字表示,另一种是使用枚举实现. 使用数字表示就是通过契约形式,约定每个数字表示的含义,接口接收到参数,就按照约定对类型进行判断,接口维护成本比较大. 在 Spring 体系中,使用枚举表示,是借助 Spring 的 Converter 机制,可以将数字或字符串对应到枚举的序号或者 name,然后将前端的输入转换为枚举类型. 在场景不复杂的场景中,枚举可以轻松胜任. 于是,迅速实现逻辑,准备提

  • Springboot集成mybatis与jsp过程详解

    目录 什么是Spring Boot? springboot特点 springboot快速搭建项目 新建项目springboot_mybatis_jsp 项目配置 配置项目目录 配置工作目录(working directory) 配置pom.xml 配置application.properties 编写代码 建表t_user 编写User.java 编写UserMapper.xml 编写UserService.java.UserServiceImpl.java 编写Controller 什么是Sp

  • springboot+rabbitmq实现智能家居实例详解

    目录 引言 一.什么是 MQTT协议? 二.为什么要用 MQTT协议? 三.MQTT协议介绍 MQTT数据包 1.固定头 2.可变头 3.消息体payload 消息质量(QoS ) 1.Qos 0 2.Qos 1 3.Qos 2 LWT(最后遗嘱) 四.MQTT协议应用场景 五.代码实现 1.启用 rabbitmq的mqtt协议 2.mqtt 客户端依赖包 3.消息发送者 4.消息订阅 六.测试消息 1.测试消息发送 2.测试消息订阅 七.应用注意事项 clientId 要唯一 八.其他中间件

  • 分布式Hibernate search详解

    分布式Hibernate Search与Apache Tomcat6,ActiveMQ 和Spring.今天我将跟大家分享我的经验,以master/slave(s)方式配置分布式Hibernate Search并整合Apache ActiveMQ,Spring,应用程序额容器是Apache Tomcat 6. 怎么工作: -Hibernate Search 支持使用JMS back-end 和 master/slave(s) 索引进行分布式配置 - mater通过网络共享暴露索引 (例如通过NF

随机推荐