seata框架
AT模式:
先添加seata需要的数据库相关表,AT模式需要在每个业务所属库下建undo_log表,用来回滚的,出错seata就会从这个表生成反向sql回退数据
建表语句:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
在每个业务库下建完undo_log后,需要建一个seata服务所用的库,我这里就叫seata库
建表语句:(注意我这个是1.6.1版本的表,可能和旧版本的字段长度什么的有差异,因为我之前随便找了sql跑seata就是报错,具体没去研究)
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(4) DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`gmt_create` datetime(6) DEFAULT NULL,
`gmt_modified` datetime(6) DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for distributed_lock
-- ----------------------------
DROP TABLE IF EXISTS `distributed_lock`;
CREATE TABLE `distributed_lock` (
`lock_key` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`lock_value` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`expire` bigint(20) DEFAULT NULL,
PRIMARY KEY (`lock_key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`gmt_create` datetime(0) DEFAULT NULL,
`gmt_modified` datetime(0) DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_status_gmt_modified`(`status`, `gmt_modified`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0:locked ,1:rollbacking',
`gmt_create` datetime(0) DEFAULT NULL,
`gmt_modified` datetime(0) DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_status`(`status`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
接下来采用nacos作为注册中心和配置中心,seata解压后修改conf下的application.yml
样例:
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
#主要注意这一段:
seata:
config: #配置中心,这里指定nacos
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP #nacos上的配置所属分组
username: ""#nacos这里未开启验证无所谓配不配
password: ""
# data-id: seataServer.properties #我测的这个版本1.6.1将官方的config.txt写在这个里seata读不到,不知道原因,后采用旧版本seata的配置方式
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7 #配置的命名空间,需要现在nacos中新建命名空间
registry:#nacos上的服务注册中心
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos #类别选择nacos
nacos:
application: seata-server #注册到nacos上的服务名
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP #服务所属组
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7 #所属命名空间,这里和配置所属的命名空间要一样
username: ""#nacos这里未开启验证无所谓配不配
password: ""
########这里的store我配了但是seata好像读不到,不知道为什么,后面还是在nacos上配的
store:
# support: file 、 db 、 redis
mode: db
db:
db-type: mysql
datasourcec: druid
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
user: root
password: root
##############################################
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
修改完application.yml后windows直接启动bin下的bat文件,然后可以看到nacos上由seata-server注册上来
然后到配置中心取做配置
注意,service.vgroupMapping.xxx,这个xxx很重要,对应意思是事务分组,
我以service.vgroupMapping.default_tx_group为例,这个default_tx_group也是1.6.1版本默认的配置
这里的default其实在seata启动的application.yml中可以配其他的,对应的是这个值
不配的话默认也是default,像上文我就没额外自己配
到这里seata-server端就启动好了,这里nacos更新配置文件seata是会动态更新的不用重启
接下来是客户端集成,就是微服务中集成seata模块
这边我以商品订单余额为例:
三个微服务,都注册在nacos上,分别为三个独立数据库
我的调用顺序为:减库存->减余额->创建订单
减库存接口:
@Autowired
ReduceGoodsService reduceGoodsService;
@RequestMapping("/reduceGoods/{goodsId}")
public String reduceGoods(@PathVariable("goodsId")String goodsId){
System.out.println("调用扣减库存");
int i = reduceGoodsService.reduceGoodsCount(goodsId);
if (i==1){
return "扣减库存成功";
}
else {
return "扣减库存失败";
}
}
减余额接口:
@Autowired
ReduceMoneyService reduceMoneyService;
@RequestMapping("/reduceMoney/{userId}")
public String reduceMoney(@PathVariable("userId")String userId){
System.out.println("调用扣减余额");
int i = reduceMoneyService.reduceRestMoney(userId);
if (i==1){
return "扣减库存成功";
}
else {
return "扣减库存失败";
}
}
创建订单接口:
@GlobalTransactional(rollbackFor = Exception.class)//这是全局事务注解***
@RequestMapping("/createOrder/{userId}/{goodsId}/{goodsCount}")
public void createOrder(@PathVariable("userId")String userId,@PathVariable("goodsId")String goodsId,@PathVariable("goodsCount")String goodsCount){
//减库存
String reduceGoodsUrl="http://"+"reduceGoods"+"/reduceGoods/"+goodsId;
restTemplate.getForObject(reduceGoodsUrl, String.class);
//减余额
String reduceMoneyUrl="http://"+"reduceMoney"+"/reduceMoney/"+userId;
restTemplate.getForObject(reduceMoneyUrl, String.class);
createOrderService.createOrder(UUID.randomUUID().toString(),userId, goodsId, goodsCount);
}
接下是重要的配置文件,以扣余额为例
server:
port: 2222
spring:
application:
name: reduceMoney
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
ip: 127.0.0.1
register-enabled: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/users?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
##########################################以上是注册服务到nacos等的相关配置,主要关注下面的seata
seata:
enable-auto-data-source-proxy: true #默认开启,如果要手动配configuration中的datasource的话要设为false
enabled: true
tx-service-group: default_tx_group #这个要和上文在nacos上注册的service.vgroupMapping.xxx中的xxx一致
##############################下面这部分和seata自己的那部分配置一样
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
username: ""
password: ""
###################################
service:
# 事务组对应的集群名称
vgroupMapping:
default_tx_group: default #这个default_tx_group和上面一样配你配在nacos中的service.vgroupMapping.xxx中的xxx一致
其他模块的seata配置和这个一样
接下来启动三个模块,然后调用,如果发现有异常,那seata会回退全局事务
在日志中体现为
如果全局事务提交成功,日志体现为
另外,在三个模块启动时,会分别注册到seata中去,日志体现为:
附录:一些坑:
如果模块启动报找不到service.vgroupMapping.xxxx大概率是配置中心配的不对,就是那个service.vgroupMapping.xxxx属性,然后服务模块中的yml一定要配这个
###################################
service:
# 事务组对应的集群名称
vgroupMapping:
default_tx_group: default
业务表如果没主键,或者主键是自增的好像seata是不能用的,如果你的整理业务流程中,涉及到error你去捕获了但是不抛出来,那seata是不会认为报错的,也就不会回滚,用try catch的话一定要throw出来