seata TCC模式

。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;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值