。Seata 产品模块
。Seata 中有三⼤模块,分别是 TM、RM 和 TC。其中 TM 和 RM 是作为 Seata 的客户端与业务系统集
成在⼀起,TC 作为 Seata 的服务端独⽴部署。
1).TC (Transaction Coordinator) - 事务协调者
维护全局和分⽀事务的状态,驱动全局事务提交或回滚。
2).TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
3).RM (Resource Manager) - 资源管理器
管理分⽀事务处理的资源,与TC交谈以注册分⽀事务和报告分⽀事务的状态,并驱动分⽀事务提交或
回滚。
1.TCC模式介绍
Seata 开源了 TCC 模式,该模式由蚂蚁⾦服贡献。TCC 模式需要⽤户根据⾃⼰的业务场景实现
Try、Confirm 和 Cancel 三个操作;事务发起⽅在⼀阶段 执⾏ Try ⽅式,在⼆阶段提交执⾏ Confirm
⽅法,⼆阶段回滚执⾏ Cancel ⽅法。
2.TCC 三个⽅法描述:
Try:资源的检测和预留;
Confirm:执⾏的业务操作提交;要求 Try 成功 Confirm ⼀定要能成功;
Cancel:预留资源释放。
⽤户接⼊ TCC ,最重要的是考虑如何将⾃⼰的业务模型拆成两阶段来实现。
以“扣钱”场景为例,在接⼊ TCC 前,对 A 账户的扣钱,只需⼀条更新账户余额的 SQL 便能完成;
但是在接⼊ TCC 之后,⽤户就需要考虑如何将原来⼀步就能完成的扣钱操作,拆成两阶段,实现成三个⽅法,并且保证⼀阶段 Try 成功的话 ⼆阶段 Confirm ⼀定能成功。
Try ⽅法作为⼀阶段准备⽅法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是
检查账户余额是否充⾜,预留转账资⾦,预留的⽅式就是冻结 A 账户的 转账资⾦。Try ⽅法执⾏之后,
账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使⽤。
⼆阶段 Confirm ⽅法执⾏真正的扣钱操作。Confirm 会使⽤ Try 阶段冻结的资⾦,执⾏账号扣款。
Confirm ⽅法执⾏之后,账号 A 在⼀阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。
如果⼆阶段是回滚的话,就需要在 Cancel ⽅法内释放⼀阶段 Try 冻结的 30 元,使账号 A 的回到
初始状态,100 元全部可⽤。
⽤户接⼊ TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个⽅
法,并且保证 Try 成功 Confirm ⼀定能成功。相对于 AT 模式,TCC 模式对业务代码有⼀定的侵⼊
性,但是 TCC 模式⽆ AT 模式的全局⾏锁,TCC 性能会⽐ AT 模式⾼很多。
实战:
1.安装TC全局事务协调器
Seata Server 就是 TC,直接从官⽅仓库下载启动即可,下载地址:https://github.com/seata/sea
ta/releases
1.1 registry.conf
Seata Server 要向注册中⼼进⾏注册,这样,其他服务就可以通过注册中⼼去发现 Seata
Server,与 Seata Server 进⾏通信。
Seata ⽀持多款注册中⼼服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。
我们项⽬中要使⽤ nacos注册中⼼,nacos服务的连接地址、注册的服务名,这需要在
seata/conf/registry.conf⽂件中进⾏配置:
registry { #注册中⼼
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# 这⾥选择 nacos 注册配置
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server" # 服务名称
serverAddr = "127.0.0.1:8848" # 服务地址
group = "SEATA_GROUP" # 分组
namespace = ""
cluster = "default" # 集群
username = "nacos" # ⽤户名
password = "nacos" # 密码
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config { #配置中⼼
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
1.2. 向nacos中添加配置信息
下载配置config.txt https://github.com/seata/seata/tree/develop/script/config-center
https://seata.io/zh-cn/docs/user/configurations.html针对每个⼀项配置介绍
将config.txt⽂件放⼊seata⽬录下⾯
修改config.txt信息
Server端存储的模式(store.mode)现有file,db,redis三种。主要存储全局事务会话信息,
分⽀事务信息, 锁记录表信息,seata-server默认是file模式。file只能⽀持单机模式, 如果想要
⾼可⽤模式的话可以切换db或者redis. (db模式下需要创建配置文件中的数据库和对应的表)
1.3 创建seata数据库
需要创建global_table/branch_table/lock_table三张表,seata1.0以上就不⾃带数
据库⽂件了,要⾃⼰去github下载,https://github.com/seata/seata/tree/develop/scr
ipt/server/db
1.4使⽤nacos-config.sh ⽤于向 Nacos 中添加配置
下载地址:https://github.com/seata/seata/tree/develop/script/config-center/nacos
。将nacos-config.sh放在seata/conf⽂件夹中
命令如下:
sh nacos-config.sh 所在目录及文件 -h localhost -p 8848 -g SEATA_GROUP -t 5a3c7d6c-f497-4d68-a71a-2e5e3340b3ca -u username -w password
-h: 主机,默认值为localhost。
-p: 端口,默认值为8848。
-g: 配置分组,默认值为“SEATA\u GROUP”。
-t: 租户信息,对应于Nacos的命名空间ID字段,默认值为’’。
-u: 用户名,nacos 1.2.0+,在权限控制上,默认值为’’。
-w: 密码,nacos 1.2.0+,在权限控制上,默认值为’’。
1.5 以上做完就可以启动seata server了
2. 添加相关依赖(涉及分布式事物的项目都需要添加)
<!--添加seata依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<!--排除低版本 -->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.3.0</version>
</dependency>
2.1 项目resources目录添加registry.conf(涉及分布式事物的项目都需要添加)。注:这个是为了TM/RM 可以找到seata服务器。和seata server的registry.conf 保持一致
2.2 添加seata分类(涉及分布式事物的项目都需要添加)分组的值要和nacos中配置的key一样。如service.vgroupMapping.my_test_tx_group 最后一个点后面的字符串就是分组的值。分组是为了资源之间相互隔离。
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
logging.level.seata=debug
2.3 在事物发起方添加@GlobalTransactional(rollbackFor = Exception.class,timeoutMills = 60000,name = “事物名称”)
3.在RM端接口上(也就是事物的被调用方)添加 Try:资源的检测和预留;Confirm:执⾏的业务操作提交; Cancel:预留资源释放。三个方法(在接口上添加)
import com.baomidou.mybatisplus.extension.service.IService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.yyj.order.entity.Order;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
/**
* @LocalTCC 该注解需要添加到上⾯描述的接⼝上,表示实现该接⼝的类被 seata 来管
理,seata 根据事务的状态,
* ⾃动调⽤我们定义的⽅法,如果没问题则调⽤ Commit ⽅法,否则调⽤ Rollback ⽅
法。
*/
@LocalTCC
public interface OrderService extends IService<Order>{
/**
* @TwoPhaseBusinessAction 描述⼆阶段提交
* name: 为 tcc⽅法的 bean 名称,需要全局唯⼀,⼀般写⽅法名即可
* commitMethod: Commit⽅法的⽅法名
* rollbackMethod:Rollback⽅法的⽅法名
* @BusinessActionContextParamete 该注解⽤来修饰 Try⽅法的⼊参,
* 被修饰的⼊参可以在 Commit ⽅法和 Rollback ⽅法中通过
BusinessActionContext 获取。
*/
@TwoPhaseBusinessAction(name="addTcc",commitMethod = "addCommit",rollbackMethod = "addRollback")
void add(@BusinessActionContextParameter(paramName = "order") Order order);
public boolean addCommit(BusinessActionContext businessActionContext) throws JsonProcessingException;
public boolean addRollback(BusinessActionContext businessActionContext);
}
4.在RM端的实现类上实现接口并写逻辑
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yyj.order.entity.Order;
import com.yyj.order.mapper.OrderMapper;
import com.yyj.order.service.OrderService;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* 添加数据status为1是真正可用数据。 try阶段 设置status为0 只是预留数据
* @param order
*/
@Override
public void add(Order order) {
order.setStatus(0); // try阶段 预检查
this.save(order);//保存订单
}
/**
* 提交阶段 设置status为1
* @param businessActionContext
* @return
* @throws JsonProcessingException
*/
@Override
public boolean addCommit(BusinessActionContext businessActionContext) throws JsonProcessingException {
String order = businessActionContext.getActionContext("order").toString();
ObjectMapper objectMapper = new ObjectMapper();
Order resultOrder = objectMapper.readValue(order, Order.class);
resultOrder = this.getById(resultOrder.getId());
if(resultOrder!=null){
resultOrder.setStatus(1); // commit阶段 提交事物
this.saveOrUpdate(resultOrder);
}
log.info("======xid="+businessActionContext.getXid()+"提交成功");
return true;
}
/**
* 回滚阶段 删除try阶段预留的数据
* @param businessActionContext
* @return
*/
@SneakyThrows
@Override
public boolean addRollback(BusinessActionContext businessActionContext) {
String order = businessActionContext.getActionContext("order").toString();
ObjectMapper objectMapper = new ObjectMapper();
Order resultOrder = objectMapper.readValue(order, Order.class);
resultOrder = this.getById(resultOrder.getId());
if(resultOrder!=null){
this.removeById(resultOrder.getId()); // 回滚阶段删除订单
}
log.info("======xid="+businessActionContext.getXid()+"回滚成功");
return true;
}
}