当前位置: 首页 > news >正文

从零开始 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+搜索+分布式

相关文章:

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

从零开始 Spring Cloud 13&#xff1a;分布式事务 1.分布式事务问题 用一个示例项目演示在分布式系统中使用事务会产生的问题。 示例项目的 SQL&#xff1a;seata_demo.sql 示例项目代码&#xff1a;seata-demo.zip 这个示例项目中的微服务的互相调用依赖于 Nacos&#xf…...

2023Node.js零基础教程(小白友好型),nodejs新手到高手,(二)NodeJS入门——buffer模块、计算机基础、fs模块、path模块

就算步子乱了又如何&#xff0c;接着跳下去就好了。——《闻香识女人》 开始 011_Buffer_介绍与创建 hello&#xff0c;大家好&#xff0c;我们来学习一下buffer。首先来看看 buffer 是一个什么东东。buffer&#xff0c;中文译为缓冲区&#xff0c;是一个类似于数组的对象&am…...

lua如何调用C/C++

1 lua vs C/C lua是脚本语言&#xff0c;优点是门槛低&#xff0c;可以热更新&#xff0c;缺点当然就是性能。C/C是编译型语言&#xff0c;有点是性能高&#xff0c;但是相对的&#xff0c;门槛高&#xff0c;技术不好的人写的代码可能还没有lua的性能高&#xff0c;容易出现c…...

简单聊一聊公平锁和非公平锁,parallel并行流

目录 一、降低锁的粒度&#xff0c;将synchronized关键字不放在方法上了&#xff0c;改为synchronized代码块。二、先区分一下公平锁和非公平锁1、公平锁2、非公平锁3、公平锁的优缺点&#xff1a;4、非公平锁的优缺点&#xff1a; 三、是否对症下药四、IntStream.rangeClosed是…...

【SpringCloud】微服务技术栈入门4 - RabbitMQ初探

目录 RabbitMQ安装 rabbitmqSpringAMQP 基础队列WorkQueue路由发布订阅 FanoutExchangeDirectExchangeTopicExchange RabbitMQ 安装 rabbitmq 首先确保自己已经安装好了 docker 是 docker 拉取镜像文件&#xff1a;docker pull rabbitmq:3-management 拉取完毕&#xff0c;打…...

cefsharp(117.2.20)cef117.2.2最新体验版

一、下载nupkg https://www.nuget.org/packages/CefSharp.WinForms/ https://www.nuget.org/packages/CefSharp.Common/ https://www.nuget.org/packages/cef.redist.x64/ https://www.nuget.org/packages/cef.redist.x86/ 此版本暂时不支持H264。上一版本支持H264 cefsharp…...

layui在上传图片在前端处理图片压缩

有的人会遇到需要在前端代码处理图片压缩的问题&#xff0c;下面给大家分享怎么处理。 // 上传图片 var image_src var IsImgDealfalse; layui.upload.render({ elem: "#{tag}{id}", url: sessionStorage.getItem(httpUrlPrefix) /upload/uploadImage, // dataT…...

js 事件参考

事件参考 事件介绍 触发事件是为了通知代码可能影响代码执行的“有趣变化”。这些可能来自用户交互&#xff0c;例如使用鼠标或调整窗口大小&#xff0c;底层环境状态的变化(例如&#xff0c;低电量或来自操作系统的媒体事件)以及其他原因。 每个事件都由一个基于Event接口的…...

卷积网络的发展历史-LeNet

简介 LeNet是CNN结构的开山鼻祖&#xff0c;第一次定义了卷积神经网络的结构。 LeNet模型包含了多个卷积层和池化层&#xff0c;以及最后的全连接层用于分类。其中&#xff0c;每个卷积层都包含了一个卷积操作和一个非线性激活函数&#xff0c;用于提取输入图像的特征。池化层…...

(2023,GPT-4V,LLM,LMM,功能和应用)大型多模态模型的黎明:GPT-4V(ision) 的初步探索

The Dawn of LMMs: Preliminary Explorations with GPT-4V(ision) 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 1.1 动机和概述 1.2 我们探索 GPT-4V 的方法 1.3…...

【C++设计模式之装饰模式:结构型】分析及示例

装饰模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许在运行时动态地给一个对象添加额外的行为。 描述 装饰模式通过创建一个包装器&#xff08;Wrapper&#xff09;来包裹原始对象&#xff0c;并在原始对象的行为前后添加额外的功能。…...

绘制散点图、曲线图、折线图和环形图失败, 设置迭代次数和进度无法保存图片

错误❌ 分别input设置&#xff08;我想知道微积分的力量&#xff09; 设1个人&#xff0c;他有每天3种方案&#xff0c;每天进步千分之一&#xff0c;千分之一&#xff0c;十万分之一等到他们迭代 200,500,1000,2000,3000,5000,9000次 他们在图片什么位置画曲线图&#xff0…...

micro-ROS中对消息的内存管理

文章目录 1.背景2.答案2.1.基本类型及其数组&#xff0c;不需要2.1.序列类型&#xff08;复合类型、复合序列类型&#xff09;&#xff0c;需要 3.内存申请方法3.1.手动申请&#xff08;Manual allocation&#xff09;3.1.工具辅助&#xff08;micro-ROS utilities&#xff09;…...

Springboot中使用拦截器、过滤器、监听器

一、Servlet、Filter&#xff08;过滤器&#xff09;、 Listener&#xff08;监听器&#xff09;、Interceptor&#xff08;拦截器&#xff09; Javaweb三大组件&#xff1a;servlet、Filter&#xff08;过滤器&#xff09;、 Listener&#xff08;监听器&#xff09; Spring…...

代码随想录二刷day45

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣70. 爬楼梯二、力扣322. 零钱兑换三、力扣279. 完全平方数 前言 一、力扣70. 爬楼梯 class Solution {public int climbStairs(int n) {int[] dp new…...

泊车功能专题介绍 ———— AVP系统基础数据交互内容

文章目录 系统架构系统功能描述云端子系统车辆子系统场端子系统用户APP 工作流程基础数据交互内容AVP 系统基础数据交互服务车/用户 - 云基础数据交互内容车位查询工作流程技术要求数据交互要求 车位预约工作流程技术要求数据交互要求 取消预约工作流程技术要求数据交互要求 泊…...

蓝桥杯每日一题2023.10.6

题目描述 门牌制作 - 蓝桥云课 (lanqiao.cn) 题目分析 #include<bits/stdc.h> using namespace std; int ans; int main() {for(int i 1; i < 2020; i ){int x i;while(x){int a x % 10;if(a 2)ans ;x / 10;}}cout << ans;return 0; } 题目描述 既约分数…...

7、【Qlib】【主要组件】Data Layer:数据框架与使用

7、【主要组件】Data Layer&#xff1a;数据框架与使用 简介数据准备Qlib 格式数据Qlib 格式数据集自动更新日频率数据将 CSV 格式转换为 Qlib 格式股票池&#xff08;市场&#xff09;多股票模式 数据API数据检索特征过滤器 数据加载器QlibDataLoaderStaticDataLoaderInterfac…...

Kubernetes安装部署 1

本文主要描述kubernetes的安装部署&#xff0c;kubernetes的安装部署主要包括三个关键组件&#xff0c;其中&#xff0c;包括kubeadm、kubelet、kubectl&#xff0c;这三个组件的功能描述如下所示&#xff1a; Kubeadm 用于启动与管理kubernetes集群 Kubelet 运行在所有集群的…...

在VS Code中优雅地编辑csv文件

文章目录 Rainbow csv转表格CSV to Tablecsv2tableCSV to Markdown Table Edit csv 下面这些插件对csv/tsv/psv都有着不错的支持&#xff0c;这几种格式的主要区别是分隔符不同。 功能入口/使用方法Rainbow csv按列赋色右键菜单CSV to Table转为ASCII表格指令CSV to Markdown …...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...