从零开始 Spring Cloud 13:分布式事务

news2024/11/18 7:34:09

从零开始 Spring Cloud 13:分布式事务

1.分布式事务问题

用一个示例项目演示在分布式系统中使用事务会产生的问题。

示例项目的 SQL:seata_demo.sql

示例项目代码:seata-demo.zip

这个示例项目中的微服务的互相调用依赖于 Nacos,所以还需要提供 Nacos。

整个项目的架构如下:

image-20210724165338958

订单服务有一个创建订单接口,这个接口会在订单表中生成订单信息,同时会依次调用账户服务和库存服务,这两个微服务会分别扣减账户的金额以及扣减库存。

在执行接口的时候,如果库存足够(小于等于10),就可以正常生成订单并完成库存扣减。但如果库存不够,就会出现订单生成、金额扣减,但库存没有成功扣减的问题。

接口调用示例可以参考新建订单接口文档。

出现这个现象的原因是订单创建、金额扣减、库存扣减这三个动作分别属于三个微服务的事务,这三个事务之间没有联系,所以当其中一个事务失败回滚时,另外两个事务不会受到影响,所以会出现数据不一致的问题。

为了解决这个问题,我们需要一个在分布式系统之上协调各个微服务事务的统一事务机制。

2.理论基础

2.1.CAP 定理

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分区容错性)

关于分布式系统的一致性、可用性以及分区容错性的详细说明,可以观看这个视频。

对于任意的分布式系统,最多仅能同时满足这三个目标中的两个:

image-20210724170517944

一般来说,由于分布式系统之间的通信必须通过网络连接,所以分区容错性(P)是不可避免的,所以一般的分布式系统要么会满足可用性和分区容错性(AP),要么会满足一致性和分区容错性(CP)。

2.2.BASE 理论

BASE 理论是现实中用 CAP 定理实现分布式系统时的一种指导思想,包含三个方面的内容:

  • Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • **Soft State(软状态):**在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致

BASE 理论可以看做是在具体工程实践中对 CAP 定理的一种妥协,即不需要提供完整系统的可用性,以及确保整个系统在任意时间都具备数据一致性。

从 BASE 理论可以派生出两种分布式事务的解决思路:

  • AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
  • CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

但不管是哪一种模式,都需要在子系统事务之间互相通讯,协调事务状态,也就是需要一个事务协调者(TC)

image-20210724172123567

这里的子系统事务,称为分支事务;有关联的各个分支事务在一起称为全局事务

3.Seata 介绍

官网地址:http://seata.io/

3.1.系统架构

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - **事务协调者:**维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM (Transaction Manager) - **事务管理器:**定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - **资源管理器:**管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

架构图:

image-20210724172326452

Seata基于上述架构提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

无论哪种方案,都离不开TC,也就是事务的协调者。

3.2.部署 TC 服务

部署 seata-tc 服务可以参考这篇文章。

4.实现分布式事务

4.1.XA 模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。

4.1.1.两阶段提交

XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。

正常情况:

image-20210724174102768

异常情况:

image-20210724174234987

一阶段:

  • 事务协调者通知每个事物参与者执行本地事务
  • 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁

二阶段:

  • 事务协调者基于一阶段的报告来判断下一步操作
    • 如果一阶段都成功,则通知所有事务参与者,提交事务
    • 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务

4.1.2.Seata 的 XA 模式

Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:

image-20210724174424070

RM一阶段的工作:

​ ① 注册分支事务到TC

​ ② 执行分支业务sql但不提交

​ ③ 报告执行状态到TC

TC二阶段的工作:

  • TC检测各分支事务执行状态

    a.如果都成功,通知所有RM提交事务

    b.如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接收TC指令,提交或回滚事务

4.1.3.优缺点

XA模式的优点:

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点:

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

4.1.4.实现

在微服务配置文件 application.yml 中添加 Seata 的相关配置:

seata:
  data-source-proxy-mode: XA # 使用 XA 模式分布式事务

在分布式事务的入口方法上添加@GlobalTransactional注解:

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
	// ...
    @Override
    @GlobalTransactional
    public Long create(Order order) {
        // 创建订单
        orderMapper.insert(order);
        try {
            // 扣用户余额
            accountClient.deduct(order.getUserId(), order.getMoney());
            // 扣库存
            storageClient.deduct(order.getCommodityCode(), order.getCount());

        } catch (FeignException e) {
            log.error("下单失败,原因:{}", e.contentUTF8(), e);
            throw new RuntimeException(e.contentUTF8(), e);
        }
        return order.getId();
    }
}

重启微服务并测试,可以观察到如果其中一个微服务执行失败,所有微服务的相关事务都会回滚,日志中会出现类似下面的信息:

io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 192.168.0.46:8091:153565015951716362 153565015951716366 jdbc:mysql:///seata_demo

4.2.AT 模式

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

4.2.1.Seata 的 AT 模型

image-20210724175327511

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

4.2.2.AT 与 XA 的区别

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

4.2.3.脏写问题

AT 模式虽然在性能上比 XA 模式更好,但问题是在隔离性上做了牺牲,所以可能会存在脏写问题,因此 AT 模式还引入了全局锁和更新后快照解决这个问题,具体可以观看这个视频。

4.2.4.实现

要实现 AT 模式,需要在 Seata 对应的数据库(seata)中添加一个管理全局锁的表:

DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table`  (
  `row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`row_key`) USING BTREE,
  INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

还需要在具体微服务使用的业务数据库(seata_demo)中添加保存更新前和更新后快照的表:

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime(6) NOT NULL COMMENT 'create datetime',
  `log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;

修改微服务的配置文件 application.yml,使用 AT 模式:

seata:
  data-source-proxy-mode: AT # 使用 AT 模式分布式事务

AT 模式是 Seata 的默认模式。

在事务的入口方上使用 @GlobalTransactional 注解标记。

实际测试,如果触发分支事务的回滚,会看到如下日志:

... : 扣款成功
... : rm handle branch rollback process:xid=192.168.0.46:8091:153565015951716372,branchId=153565015951716377,branchType=AT,resourceId=jdbc:mysql:///seata_demo,applicationData=null
... : Branch Rollbacking: 192.168.0.46:8091:153565015951716372 153565015951716377 jdbc:mysql:///seata_demo
... : xid 192.168.0.46:8091:153565015951716372 branch 153565015951716377, undo_log deleted with GlobalFinished
... : Branch Rollbacked result: PhaseTwo_Rollbacked

日志中的branchType=AT说明分支事务使用的是 AT 模式,并且触发了回滚,在回滚后删除了快照信息。

4.3.TCC 模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

4.3.1.流程分析

举例说明,假设需要用 TCC 模式实现从一个账户扣款的过程,该账户的初始金额是 100:

image-20210724182424907

首先执行 Try,冻结事务执行所需的金额,这里假设需要扣减 30 元:

image-20210724182457951

在事务的二阶段,如果所有的分支事务的 Try 都执行成功,TC 会要求所有分支事务都执行 Confirm 方法,在当前分支事务中,Confirm 方法会扣减掉冻结的金额:

image-20210724182706011

如果二阶段时,有其它事务的 Try 没有成功,TC 会要求所有的分支事务执行 Cancel 方法,即释放冻结的数据。在当前分支事务中,就是释放冻结的金额:

image-20210724182810734

4.3.2.Seata 的 TCC 模型

image-20210724182937713

4.3.3.优缺点

TCC的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点:

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

4.3.4.事务悬挂和空回滚

在实现 TCC 模式前,还需要讨论两个 TCC 模式中会遇到的两个问题:事务悬挂和空回滚。

4.3.4.1.空回滚

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做实际的回滚逻辑(因为还没有 Try),这就是空回滚

image-20210724183426891

要解决空回滚的问题,可以在数据库中记录分支事务执行的状态,在执行 Cancel 时检查分支事务是否执行过 Try,如果没有,就是空回滚,不执行具体的回滚逻辑。

4.3.4.2.事务悬挂

对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂

不存在因为分支事务阻塞但全局事务提交导致的事务悬挂问题,因为某个分支事务的阻塞必然会导致全局事务无法提交。

要解决事务悬挂,可以在 Try 方法中加入判断,如果分支事务已经执行过 Cancel 方法,就不再执行资源锁定等 Try 的正常逻辑。

4.3.5.实现

实现 TCC 模式需要注意的是,TCC 模式是有局限性的,并不能实现所有类型的分支事务,它只能应用于某些资源锁定类型的分支事务。比如在这个示例项目中,创建订单这个操作就无法使用 TCC 模式,因为没有可以锁定的资源,但是扣减账户金额和库存两个操作可以用 TCC 模式实现。因此,在实际使用中通常会将 TCC 模式和其它的两阶段事务提交模式(XA 或 AT 模式)结合使用。

这里通过对示例项目中的账户金额扣减使用 TCC 模式来说明。

首先,为了能够对账户表的金额进行冻结操作,需要创建对应的一张账户金额冻结表:

DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl`  (
  `xid` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_id` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `freeze_money` INT(11) UNSIGNED NULL DEFAULT 0,
  `state` INT(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
  PRIMARY KEY (`xid`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;

其中:

  • xid:是全局事务id
  • freeze_money:用来记录用户冻结金额
  • state:用来记录事务状态

还需要为对应的金额冻结表创建对应的 MyBatis Mapper 接口和实体类,这里不再赘述。

创建一个采用 TCC 模式实现分支事务的 Service 层接口:

@LocalTCC
public interface AccountTccService {
    /**
     * 扣减账户金额
     *
     * @param userId 账户id
     * @param money  金额
     */
    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                @BusinessActionContextParameter(paramName = "money") int money);

    /**
     * 分布式事务成功提交时执行的操作
     * @param ctx 分支事务的上下文
     * @return 分支事务提交是否成功
     */
    boolean confirm(BusinessActionContext ctx);

    /**
     * 分布式事务失败回滚时执行的操作
     * @param ctx 分支事务的上下文
     * @return 分支事务回滚是否成功
     */
    boolean cancel(BusinessActionContext ctx);
}

在这个接口中,deduct方法是 TCC 模式中的 Try 方法,confirm方法是 Confirm 方法,cancel模式上 Cancel 方法。需要用@TwoPhaseBusinessAction注解标记 TCC 模式的 Try 方法,并且其属性name对应 Try 方法的方法名,commitMethod对应 Confirm 方法的方法名,rollbackMethod对应 Cancel 方法的方法名。

@BusinessActionContextParameter注解标记 Try 方法参数后,相应的参数值会保存到分支事务的上下文中。相应的,Confirm 方法和 Cancel 方法可以通过形参BusinessActionContext获取到分支事务的上下文,并从中获取 Try 方法的参数。

实现该接口:

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Service
public class AccountTccServiceImpl implements AccountTccService {
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountFreezeMapper accountFreezeMapper;

    @Override
    @Transactional
    public void deduct(String userId, int money) {
        // 获取当前的全局事务id
        String xid = RootContext.getXID();
        // 执行 try 逻辑
        // 尝试扣减剩余金额,如果扣减失败,数据库会报错
        accountMapper.deduct(userId, money);
        // 添加冻结金额和 Try 执行记录
        AccountFreeze af = new AccountFreeze();
        af.setState(AccountFreeze.State.TRY);
        af.setFreezeMoney(money);
        af.setUserId(userId);
        af.setXid(xid);
        accountFreezeMapper.insert(af);
    }

    @Override
    public boolean confirm(BusinessActionContext ctx) {
        // 删除冻结金额和 Try 执行记录
        int rows = accountFreezeMapper.deleteById(ctx.getXid());
        return rows == 1;
    }

    @Override
    public boolean cancel(BusinessActionContext ctx) {
        // 查询冻结数据
        String xid = ctx.getXid();
        AccountFreeze af = accountFreezeMapper.selectById(xid);
        if (af != null) {
            // 冻结金额归零,并且分支事务状态修改为 cancel
            AccountFreeze newAf = new AccountFreeze();
            newAf.setXid(xid);
            newAf.setState(AccountFreeze.State.CANCEL);
            newAf.setFreezeMoney(0);
            accountFreezeMapper.updateById(newAf);
            // 恢复可用金额
            int rows = accountMapper.refund(af.getUserId(), af.getFreezeMoney());
            return rows == 1;
        }
        return true;
    }
}

上面的实现是没有考虑空回滚和事务悬挂的实现,因此还需要做进一步修改。

处理空回滚:

@Override
public boolean cancel(BusinessActionContext ctx) {
    // 查询冻结数据
    String xid = ctx.getXid();
    AccountFreeze af = accountFreezeMapper.selectById(xid);
    if (af == null) {
        // 没有分支事务执行记录时触发 Cancel,是空回滚
        // 只记录 Cancel 执行,不做业务处理
        AccountFreeze newAf = new AccountFreeze();
        newAf.setXid(xid);
        newAf.setUserId(ctx.getActionContext("userId").toString());
        newAf.setFreezeMoney(0);
        newAf.setState(AccountFreeze.State.CANCEL);
        accountFreezeMapper.insert(newAf);
        return true;
    }
    // 冻结金额归零,并且分支事务状态修改为 cancel
    AccountFreeze newAf = new AccountFreeze();
    newAf.setXid(xid);
    newAf.setState(AccountFreeze.State.CANCEL);
    newAf.setFreezeMoney(0);
    accountFreezeMapper.updateById(newAf);
    // 恢复可用金额
    int rows = accountMapper.refund(af.getUserId(), af.getFreezeMoney());
    return rows == 1;
}

处理事务悬挂:

@Override
@Transactional
public void deduct(String userId, int money) {
    // 获取当前的全局事务id
    String xid = RootContext.getXID();
    // 检查分支事务是否已经执行过 Cancel,如果是,就是业务悬挂,不进行任何处理
    AccountFreeze accountFreeze = accountFreezeMapper.selectById(xid);
    if (accountFreeze != null && accountFreeze.getState() == AccountFreeze.State.CANCEL) {
        return;
    }
    // 执行 try 逻辑
    // 尝试扣减剩余金额,如果扣减失败,数据库会报错
    accountMapper.deduct(userId, money);
    // 添加冻结金额和 Try 执行记录
    AccountFreeze af = new AccountFreeze();
    af.setState(AccountFreeze.State.TRY);
    af.setFreezeMoney(money);
    af.setUserId(userId);
    af.setXid(xid);
    accountFreezeMapper.insert(af);
}

虽然解决了空回滚和事务悬挂,但我们还需要确保 Confirm 和 Cancel 操作具备幂等性,因为 TC 可能会在调用超时后重复执行调用。

解决幂等性:

@Override
public boolean confirm(BusinessActionContext ctx) {
    String xid = ctx.getXid();
    // 确保幂等性,如果金额冻结记录已经被删除,直接返回成功
    AccountFreeze af = accountFreezeMapper.selectById(xid);
    if (af == null) {
        return true;
    }
    // 删除冻结金额和 Try 执行记录
    int rows = accountFreezeMapper.deleteById(xid);
    return rows == 1;
}

@Override
public boolean cancel(BusinessActionContext ctx) {
    // 查询冻结数据
    String xid = ctx.getXid();
    AccountFreeze af = accountFreezeMapper.selectById(xid);
    if (af == null) {
        // 没有分支事务执行记录时触发 Cancel,是空回滚
        // 只记录 Cancel 执行,不做业务处理
        AccountFreeze newAf = new AccountFreeze();
        newAf.setXid(xid);
        newAf.setUserId(ctx.getActionContext("userId").toString());
        newAf.setFreezeMoney(0);
        newAf.setState(AccountFreeze.State.CANCEL);
        accountFreezeMapper.insert(newAf);
        return true;
    }
    // 确保幂等性,如果已经执行过 Cancel,不再执行相关逻辑,直接返回成功
    if (AccountFreeze.State.CANCEL == af.getState()) {
        return true;
    }
    // 冻结金额归零,并且分支事务状态修改为 cancel
    AccountFreeze newAf = new AccountFreeze();
    newAf.setXid(xid);
    newAf.setState(AccountFreeze.State.CANCEL);
    newAf.setFreezeMoney(0);
    accountFreezeMapper.updateById(newAf);
    // 恢复可用金额
    int rows = accountMapper.refund(af.getUserId(), af.getFreezeMoney());
    return rows == 1;
}

最后,在 Controller 中使用 TCC 模式实现的 Service:

@RestController
@RequestMapping("account")
public class AccountController {

    @Autowired
    private AccountTccService accountService;

    @PutMapping("/{userId}/{money}")
    public ResponseEntity<Void> deduct(@PathVariable("userId") String userId, @PathVariable("money") Integer money){
        accountService.deduct(userId, money);
        return ResponseEntity.noContent().build();
    }
}

4.4.SAGA 模式

Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。

其理论基础是Hector & Kenneth 在1987年发表的论文Sagas。

Seata官网对于Saga的指南:https://seata.io/zh-cn/docs/user/saga.html

4.4.1.原理

在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。

image-20210724184846396

Saga也分为两个阶段:

  • 一阶段:直接提交本地事务
  • 二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚

4.4.2.优缺点

优点:

  • 事务参与者可以基于事件驱动实现异步调用,吞吐高
  • 一阶段直接提交事务,无锁,性能好
  • 不用编写TCC中的三个阶段,实现简单

缺点:

  • 软状态持续时间不确定,时效性差
  • 没有锁,没有事务隔离,会有脏写

4.5.四种模式对比

image-20210724185021819

5.高可用

Seata 同样可以通过集群部署以实现高可用,并且还可以结合 Nacos 的配置热更新功能实现异地容灾。

具体可以参考这篇文章 以及这个视频。

本文的完整示例代码可以从这里获取。

6.参考资料

  • SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1065231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

低代码平台如何借助Nginx实现网关服务

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 在典型的系统部署架构中&#xff0c;应用服务器是一种软件或硬件系统&#xff0c…

解决Ubuntu18.04安装好搜狗输入法后无法打出中文的问题

首先下载安装 搜狗拼音输入法 &#xff0c;下载选择&#xff1a; x86_64 在ubuntu中设置 fcitx 最后发现安装好了&#xff0c;图标有了 &#xff0c;但是使用时不能输入中文&#xff0c;使用下面的命令解决&#xff1a; sudo apt install libqt5qml5 libqt5quick5 libqt5qu…

PyTorch深度学习实战(20)——从零开始实现R-CNN目标检测

PyTorch深度学习实战&#xff08;20&#xff09;——从零开始实现R-CNN目标检测 0. 前言1. R-CNN 目标检测模型1.1 核心思想1.2 算法流程 2. 实现 R-CNN 目标检测2.1 数据集准备2.2 获取区域提议和偏移量2.3 创建训练数据2.4 构建 R-CNN 架构 3. R-CNN目标检测模型测试小结系列…

【JUC系列-09】深入理解ReentrantReadWriteLock的底层实现

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

浅谈智能安全配电装置在老年人建筑中的应用

摘要&#xff1a;我国每年因触电伤亡人数非常多&#xff0c;大多数事故是发生在用电设备和配电装置。在电气事故中&#xff0c;无法预料和不可抗拒的事故是比较少的&#xff0c;大量用电事故可采取切实可行措施来预防。本文通过结合老年人建筑的特点和智能安全配电装置的功能&a…

教你三步搞定VsCode调试C++

目录 1 配置编译任务2 配置调试任务3 进行调试 1 配置编译任务 使用VsCode进行C开发时&#xff0c;除了在机器上安装必要的编译工具&#xff08;例如&#xff0c;gcc、g、cmake等&#xff09;之外&#xff0c;还需要在VsCode配置编译任务&#xff0c;从而可以通过点击或者快捷…

【MySql】mysql之进阶查询语句

目录 一、常用查询 1、order by按关键字排序❤ 1.1 升序排序 1.2 降序排序 1.3 结合where进项条件过滤再排序 1.4 多字段排序 2、and和or判断 2.1 and和or的使用 2.2 嵌套、多条件使用 3、distinct 查询不重复记录 4、group by 对结果进行分组 5、limit限制结果…

MySQL57部署与配置[Windows10]

下载原始安装包 https://dev.mysql.com/downloads/installer/https://downloads.mysql.com/archives/notifier/默认安装 MySQL57 默认安装 MySQL Notifier 环境变量配置 Path: C:\Program Files\MySQL\MySQL Server 5.7\binDBeaver数据库连接

【MySql】4- 实践篇(二)

文章目录 1. SQL 语句为什么变“慢”了1.1 什么情况会引发数据库的 flush 过程呢&#xff1f;1.2 四种情况性能分析1.3 InnoDB 刷脏页的控制策略 2. 数据库表的空间回收2.1 innodb_file_per_table参数2.2 数据删除流程2.3 重建表2.4 Online 和 inplace 3. count(*) 语句怎样实现…

websocket拦截

python实现websocket拦截 前言一、拦截的优缺点优点缺点二、实现方法1.环境配置2.代码三、总结现在的直播间都是走的websocket通信,想要获取websocket通信的内容就需要使用websocket拦截,大多数是使用中间人代理进行拦截,这里将会使用更简单的方式进行拦截。 前言 开发者工…

RK3568平台开发系列讲解(外设篇)AP3216C 三合一环境传感器驱动

🚀返回专栏总目录 文章目录 一、AP3216C 简介二、AP3216C驱动程序2.1、设备树修改2.2、驱动程序沉淀、分享、成长,让自己和他人都能有所收获!😄 📢在本篇将介绍AP3216C 三合一环境传感器的驱动。 一、AP3216C 简介 AP3216C 是由敦南科技推出的一款传感器,其支持环境光…

OpenWrt使用Privoxy插件修改UA

OpenWrt使用privoxy修改UA 1.安装privoxy插件 SSH连接到路由器 更新插件列表 update opkg安装插件 opkg install privoxy luci-app-privoxy luci-i18n-privoxy-zh-cn重启路由器 2.配置privoxy 打开配置页面 文件和目录 访问和控制 转发 杂项 日志 编辑配置 浏览器打开 …

Kaggle - LLM Science Exam(一):赛事概述、数据收集、BERT Baseline

文章目录 一、赛事概述1.1 OpenBookQA Dataset1.2 比赛背景1.3 评估方法和代码要求1.4 比赛数据集1.5 优秀notebook 二、BERT Baseline2.1 数据预处理2.2 定义data_collator2.3 加载模型&#xff0c;配置trainer并训练2.4 预测结果并提交2.5 deberta-v3-large 1k Wiki&#xff…

深入理解Linux网络笔记(三):内核和用户进程协作之epoll

本文为《深入理解Linux网络》学习笔记&#xff0c;使用的Linux源码版本是3.10&#xff0c;网卡驱动默认采用的都是Intel的igb网卡驱动 Linux源码在线阅读&#xff1a;https://elixir.bootlin.com/linux/v3.10/source 2、内核是如何与用户进程协作的&#xff08;二&#xff09; …

Godot 官方2D游戏笔记(1):导入动画资源和添加节点

前言 Godot 官方给了我们2D游戏和3D游戏的案例&#xff0c;不过如果是独立开发者只用考虑2D游戏就可以了&#xff0c;因为2D游戏纯粹&#xff0c;我们只需要关注游戏的玩法即可。2D游戏的美术素材简单&#xff0c;交互逻辑简单&#xff0c;我们可以把更多的时间放在游戏的玩法…

苍穹外卖

1、基础知识扫盲 项目从0到1 需求分析->设计->编码->测试->上线运维 角色 项目经理&#xff1a;对整个项目负责&#xff0c;任务分配&#xff0c;把控进度 产品经理&#xff1a;进行需求调研&#xff0c;输出需求调研文档&#xff0c;产品原型 UI设计师&…

【java计算机毕设】 留守儿童爱心捐赠管理系统 springboot vue html mysql 送文档ppt

1.项目视频演示 【java计算机毕设】留守儿童爱心捐赠管理系统 springboot vue html mysql 送文档ppt 2.项目功能截图 3.项目简介 后端&#xff1a;springboot&#xff0c;前端&#xff1a;vue&#xff0c;html&#xff0c;数据库&#xff1a;mysql&#xff0c;开发软件idea 留…

Springboot使用Aop保存接口请求日志到mysql

1、添加aop依赖 <!-- aop日志 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency> 2、新建接口保存数据库的实体类RequestLog.java package com.example…

volatile关键字使用总结

先说结论 1. volatile关键字可以让编译器层面减少优化&#xff0c;每次使用时必须从内存中取数据&#xff0c;而不是从cpu缓存或寄存器中获取 2. volatile关键字不能完全禁止指令重排&#xff0c;准确地说是两个volatile修饰的变量之间的命令不会进行指令重排 3. 使用volati…

BLE协议栈1-物理层PHY

从应届生开始做ble开发也差不读四个月的时间了&#xff0c;一直在在做上层的应用&#xff0c;对蓝牙协议栈没有过多的时间去了解&#xff0c;对整体的大方向概念一直是模糊的状态&#xff0c;在开发时也因此遇到了许多问题&#xff0c;趁有空去收集了一下资料来完成了本次专栏&…