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

从零开始 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@GlobalTransactionalpublic 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 {@Autowiredprivate AccountMapper accountMapper;@Autowiredprivate AccountFreezeMapper accountFreezeMapper;@Override@Transactionalpublic void deduct(String userId, int money) {// 获取当前的全局事务idString 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);}@Overridepublic boolean confirm(BusinessActionContext ctx) {// 删除冻结金额和 Try 执行记录int rows = accountFreezeMapper.deleteById(ctx.getXid());return rows == 1;}@Overridepublic boolean cancel(BusinessActionContext ctx) {// 查询冻结数据String xid = ctx.getXid();AccountFreeze af = accountFreezeMapper.selectById(xid);if (af != null) {// 冻结金额归零,并且分支事务状态修改为 cancelAccountFreeze 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;}// 冻结金额归零,并且分支事务状态修改为 cancelAccountFreeze 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) {// 获取当前的全局事务idString 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;}// 冻结金额归零,并且分支事务状态修改为 cancelAccountFreeze 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 {@Autowiredprivate 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.xdnf.cn/news/150454.html

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

相关文章

Qt之显示PDF文件

之前使用过mupdf库&#xff0c;能够成功显示pdf&#xff0c;但是我用着有BUG&#xff0c;不太理解它的代码&#xff0c;搞了好久都不行。后面又试了其他库&#xff0c;如pdfium、popler、下载了很多例程&#xff0c;都跑不起来&#xff01;后面偶然得知xpdf库&#xff0c;看起来…

利用C++开发一个迷你的英文单词录入和测试小程序-升级版本

我们现在有了一个本地sqlite3的迷你英文单词小测试工具&#xff0c;需求就跟工作当中一样是不断变更的。这里虚构两个场景&#xff0c;并且一步一步的完成最终升级后的小demo。 场景&#xff1a;数据不依赖本地sqlite3&#xff0c;需要支持远程访问&#xff0c;用目前的restfu…

做外贸独立站选Shopify还是WordPress?

现在确实会有很多新人想做独立站&#xff0c;毕竟跨境电商平台内卷严重&#xff0c;平台规则限制不断升级&#xff0c;脱离平台“绑架”布局独立站&#xff0c;才能获得更多流量、订单、塑造品牌价值。然而&#xff0c;在选择建立外贸独立站的过程中&#xff0c;选择适合的建站…

C# 替换字符串最后一个逗号为分号

使用场景&#xff0c;sql语句的insert into table(c1,c2,c3) values (v1,v2,v3),(v1,v2,v3),(v1,v2,v3), 为了提高执行效率&#xff0c;在一个insert into中执行时&#xff0c;在循环中拼接语句&#xff0c;最后一个逗号需要替换为分号才能执行。 public static string Replace…

JVM技术文档--JVM诊断调优工具Arthas--阿里巴巴开源工具--一文搞懂Arthas--快速上手--国庆开卷!!

​ Arthas首页 简介 | arthas Arthas官网文档 Arthas首页、文档和下载 - 开源 Java 诊断工具 - OSCHINA - 中文开源技术交流社区 阿丹&#xff1a; 之前聊过了一些关于JMV中的分区等等&#xff0c;但是有同学还是在后台问我&#xff0c;还有私信问我&#xff0c;学了这些…

Java多线程篇(7)——AQS之共享锁(Semaphore、CountDownLatch)

文章目录 1、Semaphore1.1、acquire1.2、release 2、CountDownLatch2.1、await2.2、countDown 1、Semaphore 1.1、acquire Semaphore.acquire public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}AbstractQueuedSynchronizer.acquireSh…

pytorch算力与有效性分析

pytorch Windows中安装深度学习环境参考文档机器环境说明3080机器 Windows11qt_env 满足遥感CS软件分割、目标检测、变化检测的需要gtrs 主要是为了满足遥感监测管理平台&#xff08;BS&#xff09;系统使用的&#xff0c;无深度学习环境内容swin_env 与 qt_env 基本一致od 用于…

RabbitMQ之Direct(直连)Exchange解读

目录 基本介绍 使用场景 springboot代码演示 演示架构 工程概述 RabbitConfig配置类&#xff1a;创建队列及交换机并进行绑定 MessageService业务类&#xff1a;发送消息及接收消息 主启动类RabbitMq01Application&#xff1a;实现ApplicationRunner接口 基本介绍 在r…

【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限制结果…

K8S:配置资源管理 Secret和configMap

文章目录 一.Secret1.Secret概念2.Secret的类型①kubernetes.io/service-account-token②opaque③kubernetes.io/dockerconfigjson④kubernetes.io/tls 3.secret的三种参数①tls②docker-registry③generic 4.Pod 的3种方式来使用secret5.Secret创建及案例&#xff08;1&#x…

算法题:柠檬水找零

这道题就是纯贪心算法题&#xff0c;遍历每个顾客&#xff0c;先把钱收了&#xff0c;如果是10块钱就判断手里头有没有5元用于找零&#xff1b;如果是20块钱&#xff0c;先判断是不是有10元5元&#xff0c;如果没有就再判断是否有3个5元。没有的话就直接返回 False。(完整题目附…

模型压缩部署概述

模型压缩部署概述 一&#xff0c;模型在线部署 1.1&#xff0c;深度学习项目开发流程 1.2&#xff0c;模型训练和推理的不同 二&#xff0c;手机端CPU推理框架的优化 三&#xff0c;不同硬件平台量化方式总结 参考资料 一&#xff0c;模型在线部署 深度学习和计算机视觉…

excel中将一个sheet表根据条件分成多个sheet表

有如下excel表&#xff0c;要求&#xff1a;按月份将每月的情况放在一个sheet中。 目测有6个月&#xff0c;就应该有6个sheet&#xff0c;每个sheet中体现本月的情况。 一、首先增加一个辅助列&#xff0c;月份&#xff0c;使用month函数即可。 填充此列所有。然后复制【月份】…

QT内存管理

Qt的半自动化的内存管理 &#xff08;1&#xff09;QObject及其派生类的对象&#xff0c;如果其parent非0&#xff0c;那么其parent析构时会析构该对象。 &#xff08;2&#xff09;QWidget及其派生类的对象&#xff0c;可以设置 Qt::WA_DeleteOnClose 标志位(当close时会析构…

代码随想录算法训练营第23期day14|二叉树层序遍历、226.翻转二叉树、101. 对称二叉树

目录 一、二叉树层序遍历 非递归法 递归法 相关题目&#xff08;10题&#xff09; 二、&#xff08;leetcode 226&#xff09;翻转二叉树 递归法 层序遍历 深度优先遍历 1&#xff09;非统一写法——前序遍历 2&#xff09; 统一写法——前序遍历 三、&#xff08;le…

C++设计模式-享元(Flyweight)

目录 C设计模式-享元&#xff08;Flyweight&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-享元&#xff08;Flyweight&#xff09; 一、意图 运用共享技术有效地支持大量细粒度的对象。 二、适用性 一个应用程序使用了大量的对象。完全由…

十一工具箱流量主小程序源码

无授权&#xff0c;去过滤机制版本 看到网上发布的都是要授权的 朋友叫我把他去授权&#xff0c;能用就行 就把过滤去了 这样就不用授权 可以免费使用 白嫖党专属 一切接口可用&#xff0c;无需担心不能用 授权者不关站一直可以用 源码下载&#xff1a;https://download.csdn.…

抄写Linux源码(Day19:读取硬盘前的准备工作有哪些?)

回忆我们需要做的事情&#xff1a; 为了支持 shell 程序的执行&#xff0c;我们需要提供&#xff1a; 1.缺页中断(不理解为什么要这个东西&#xff0c;只是闪客说需要&#xff0c;后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的&#xff0c;所以需要这两个东…

在WIN10平台上体验Microsoft古老的Quick C 1.0编程

前言&#xff1a; 90年代初&#xff0c;微软出了Quick系统对抗Borland Turbo系列&#xff0c;其中包括 QuickBasic, QuickPascal和Quick C。1991年&#xff0c;Quick C for Windows 1.0发布&#xff0c;后来它被Visual C取代。我自己觉得微软成就在那个winstub.exe桩上&#xf…

Connect to 127.0.0.1:1080 [/127.0.0.1] failed: Connection refused: connect

报错信息 A problem occurred configuring root project CourseSelection. > Could not resolve all artifacts for configuration :classpath.> Could not resolve com.android.tools.build:gradle:3.6.1.Required by:project :> Could not resolve com.android.tool…