**
分布式-分布式事务(接上篇 分布式-Ribbon 负载均衡)
**
现象:当有服务A调用服务B的情况,如果 A或者B业务出错时,整个业务A和B都应该回滚,Transtional做不到这点
解决:引入第三方应用
方案:
1.二段提交(CanCommit,DoCommit):当A 调用B时,向第三方应用开启事务组,将A,B加入事务组,当发送业务出错时,第三方广播回滚命令至A,B,A,B事务回滚
2.三段提交(CanCommit、PreCommit、DoCommit):在二段提交的前提上,在DoCommit前面多一个PreCommit,协调者和参与者都引入了超时机制
3.TCC(Try、Confirm、Cancel):举个例子,A向着B转40,A向C转70,但是A只有100,当tyrCommoit时,A先扣除40到冻结金额,再扣除70到冻结金额时发现钱不够,,将冻结金额还给A,事务失败。
TCC三个操作描述:
1)Try: 检测、预留资源;
2)Confirm: 业务系统执行提交;默认Confirm阶段是不会出错的,只要TRY成功,CONFIRM一定成功;
3)Cancel: 业务取消,预留资源释放;
TX-LCN框架介绍:TX-LCN分布式事务框架,LCN并不生产事务,LCN只是本地事务的协调工,LCN是一个高性能的分布式事务框架,兼容dubbo、springcloud框架,支持RPC框架拓展,支持各种ORM框架、NoSQL、负载均衡、事务补偿
创建数据库tx-manager,数据库下创建表 t_tx_exception
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
创建第三方应用:
一:引入依赖
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
二,配置:
spring.application.name=txlcn-tm
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=validate
# TM后台登陆密码
tx-lcn.manager.admin-key=123456
tx-lcn.manager.host=127.0.0.1
tx-lcn.manager.port=8070
# 开启日志,默认为false
tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}logging.level.com.codingapi.txlcn=DEBUG
#redis 主机
spring.redis.host=127.0.0.1
#redis 端口
spring.redis.port=6379
#redis 密码
spring.redis.password=
三,在启动类添加注解 @EnableTransactionManagerServer
四,访问后台 http://127.0.0.1:7970/admin/index.html
设置两个tc
第一个TC(之前的service-a):
1.引入依赖
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.添加配置:
# 是否启动LCN负载均衡策略(优化选项,开启与否,功能不受影响)
tx-lcn.ribbon.loadbalancer.dtx.enabled=true
# 默认之配置为TM的本机默认端口
tx-lcn.client.manager-address=127.0.0.1:8070
# 开启日志,默认为false
tx-lcn.logger.enabled=true
#mysql连接属性,直接填具体的就好
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
logging.level.com.codingapi.txlcn=DEBUG
3.提供接口,书写service
@LcnTransaction//分布式事务
@Override
public String txlcn(String exFlag) {
//先调用本地服务,新增一个用户user PS:调用EntityManager的merge,传进去的实体字段是什么就保存什么
TbUser tbUser = new TbUser();
tbUser.setUsername("huanzi");
tbUser.setPassword("123456");
//持久化
TbUser user = entityManager.merge(tbUser);
System.out.println(user);
//调用B服务,新增一个用户描述description
TbDescription description1 = bFeign.txlcn(user.getId());
System.out.println(description1);
//根据标识,是否抛出异常
if (StringUtils.isEmpty(exFlag)) {
return "操作成功,请查看一下数据库验证!";
} else {
throw new RuntimeException("rollback transactional by exFlag");
}
}
4.在启动类上使用 @EnableDistributedTransaction
第二个TC(service-b)
配置信息跟第一个TC一样
提供消费接口,书写service
@LcnTransaction//分布式事务
@Override
public TbDescription txlcn(Integer userId) {
TbDescription tbDescription = new TbDescription();
tbDescription.setUserId(userId);
tbDescription.setDescription("服务B设置的描述");
TbDescription merge = entityManager.merge(tbDescription);
//B服务报错
return merge;
}
开启注册服务,消费者,提供者,第三方事务管理者
访问消费者接口http://127.0.0.1:10081/txlcn?exFlag=test
访问接口:http://127.0.0.1:10081/txlcn
建表语句:
service-a 的tb_user:
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表id',
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`created` datetime NULL DEFAULT NULL COMMENT '创建时间',
`description_id` int(11) NULL DEFAULT NULL COMMENT '关联详情id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 45 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Compact;
service-b的tb_description:
CREATE TABLE tb_description
(
id
int(11) NOT NULL AUTO_INCREMENT,
user_id
int(11) NOT NULL,
description
varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;