Seata
是 阿里巴巴开源的分布式事务中间件,以高效而且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。node
事实上,官方在GitHub
已经给出了多种环境下的Seata
应用示例项目,地址:https://github.com/seata/seata-samples
。mysql
为何笔者要从新写一遍呢,主要缘由有两点:git
本文涉及软件环境以下:github
为了简化流程,咱们只须要订单和库存两个服务。建立订单的时候,调用库存服务,扣减库存。spring
涉及的表设计以下:sql
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;
复制代码
打开https://github.com/seata/seata/releases
,目前最新版本是v0.6.1
。apache
下载解压后,到seata-server-0.6.1\distribution\bin
目录下能够看到seata-server.bat和seata-server.sh
,选择一个双击执行。json
不出意外的话,当你看到-Server started ...
等字样,就正常启动了。springboot
因为是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
等就不粘了,自行引入便可。
这里只须要配置数据库链接信息和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
复制代码
Seata 是经过代理数据源实现事务分支,因此须要先配置一个数据源的代理,不然事务不会回滚。
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
复制代码
注意,这里的DataSourceProxy
类位于io.seata.rm.datasource
包内。
还须要配置全局事务扫描器。有两个参数,一个是应用名称,一个是事务分组。
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
return new GlobalTransactionScanner("springboot-order", "my_test_tx_group");
}
复制代码
事实上,关于Seata事务的一系列初始化工做都在这里完成。
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
}
复制代码
在库存服务中,拿到商品编码和购买总个数,扣减便可。
<update id="decreaseStorage">
update t_storage set count = count-${count} where commodity_code = #{commodityCode}
</update>
复制代码
而后用Dubbo将库存服务扣减接口暴露出去。
在订单服务中,先扣减库存,再建立订单。最后抛出异常,而后去数据库检查事务是否回滚。
@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
System.out.println("开始全局事务。XID="+RootContext.getXID());
StorageDTO storageDTO = new StorageDTO();
storageDTO.setCount(orderDTO.getCount());
storageDTO.setCommodityCode(orderDTO.getCommodityCode());
//一、扣减库存
storageDubboService.decreaseStorage(storageDTO);
//二、建立订单
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进行传播。
请切记,Seata 是经过代理数据源实现事务分支,必定不要忘记配置数据源代理。
在数据库中,表里的主键ID字段都是自增的。若是你的字段不是自增的,那么在Mybatis的insert SQL
中,要将列名写完整。
好比咱们能够这样写SQL:
INSERT INTO table_name VALUES (值1, 值2,....)
复制代码
那么这时候就要写成:
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
复制代码
在订单表中,amount
字段类型为double
。在seata0.6.1
版本中,默认的序列化方式为fastjson
,但它会将这个字段序列化成bigdecimal
类型,会致使后面类型不匹配。
可是在后续的seata0.7.0
版本中(还未发布),已经将默认的序列化方式改成了jackson
。
不过无需担忧,这个问题通常不会出现。笔者是由于引错了一个包,才致使发现这问题。
本文示例代码在:https://github.com/taoxun/springboot-dubbo-zookeeper-seata
。
欢迎有问题及时交流~