当前位置: 首页 > 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 …...

LCR 128.库存管理 I

​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;LCR 128. 库存管理 I - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 数组可以分割成两段的升序连续子数组&#xff0c;找到两个子数组的开始元素并返回较小者即可。 解题代码&#xff1a; …...

eigen::Affine3d 转换

平移eigen::vector3d和四元数Eigen::Quaterniond 转 eigen::Affine3d Eigen::Vector3d t Eigen::Vector3d::Zero(); Eigen::Quaterniond q Eigen::Quaterniond ::Identity();Eigen::Affine3d affine3d t * q.toRotationMatrix(); Eigen::Matrix4d 转 eigen::Affine3d Eige…...

【Python从入门到进阶】38、selenium关于Chrome handless的基本使用

接上篇《37、selenium关于phantomjs的基本使用》 上一篇我们介绍了有关phantomjs的相关知识&#xff0c;但由于selenium已经放弃PhantomJS&#xff0c;本篇我们来学习Chrome的无头版浏览器Chrome Handless的使用。 一、Chrome Headless简介 Chrome Headless是一个无界面的浏览…...

给Python项目创建一个虚拟环境(enev)

给Python项目创建一个虚拟环境&#xff08;enev&#xff09; 为您的Python项目创建一个虚拟环境是一种良好的实践&#xff0c;可以隔离项目的依赖项&#xff0c;以确保它们不会干扰全局Python环境或其他项目。您可以使用venv模块来创建虚拟环境。以下是在Linux上创建虚拟环境的…...

【RK3588】YOLO V5在瑞芯微板子上部署问题记录汇总

YOLO V5训练模型部署到瑞芯微的板子上面&#xff0c;官方是有给出案例和转过详情的。并且也提供了Python版本的推理代码&#xff0c;以及C语言的代码。 但是&#xff0c;对于转换过程中的细节&#xff0c;哪些需要改&#xff1f;怎么改&#xff1f;如何改&#xff0c;和为什么…...

别人做的百度百科词条信息不全,如何更正自己的百度百科词条

很多人自己的百度百科词条是别人上传上去的&#xff0c;自己压根不知道&#xff0c;而且里面的信息内容要么不全&#xff0c;要么是有错漏的&#xff0c;但自己想要更正自己的百度百科词条又不知道如何更正&#xff0c;下面洛希爱做百科网和大家介绍一些百科经验知识。 首先百…...

[论文精读]U-Net: Convolutional Networks for BiomedicalImage Segmentation

论文原文&#xff1a;U-Net: Convolutional Networks for Biomedical Image Segmentation (arxiv.org) 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔…...

Godot Identifier “File“ not declared in the current scope.

解决方案&#xff1a; f FileAccess.open(savedir, FileAccess.READ)...

Java ORM Bee,多表关联更新

Bee V2.1.8 增加支持多表的update, insert, delete; 使用FK注解进行关联. 如果子实体没有用上FK声明的字段(即FK的字段没有值),则不执行,防止更新到多余记录 外键有一个没有设置时&#xff0c;跳过。 更多实例,请查看样例工程:https://gitee.com/automvc/bee-exam 或:h…...

Java 读取excel文件

导入&#xff1a; 先导入依赖&#xff1a; <!-- 文件上传 --> <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpmime</artifactId><version>4.5.7</version> </dependency> <!-- JSON -…...

河南建设教育中心网站/我想做地推怎么找渠道

链接: https://www.luogu.org/problem/P5410#submit 题意: 有两个字符串aa,bb,要求输出bb与aa的每一个后缀的最长公共前缀 思路: 扩展kmp模板, 上一个大佬的详解链接https://segmentfault.com/a/1190000008663857 代码: #include <bits/stdc.h> using namespace std; con…...

网站建设方案范文/sem和seo是什么意思

这个想法是让客户可能有多个地址。所以&#xff0c;当你看到客户的“个人资料”时&#xff0c;你有一个选择框&#xff0c;其所有的他/她的地址被标记&#xff0c;所以你只能看到被选中的那个。他们真的存在但隐藏着一些javascript/jquery。因此&#xff0c;添加新地址时出现问…...

软件开发和网站建设那个好/百度文库官网

虚拟内存对于任何版本的Windows而言都是十分重要的。如果设置得当&#xff0c;它将极大地提升电脑的性能和运行速度。可是在默认状态下&#xff0c;Windows始终将虚拟内存设为物理内存的1.5倍。这样的话&#xff0c;如果用户安装2GB的内存&#xff0c;系统就会腾出高达3GB的硬盘…...

河南平台网站建设公司/郑州网络营销策划

http://www.zhonehome.com/go.txt转载于:https://www.cnblogs.com/pg5yl8/archive/2009/04/20/1439576.html...

免费的wordpress采集/住房和城乡建设部官网

上回写了一篇推模型的内容&#xff0c;这回分享一篇拉模型的内容。 拉模型 拉模型就是展示微博的时候&#xff0c;获取自己的所有关注的人&#xff0c;然后从关注的人中拉取最新微博。 微博项目数据结构设计 user表设计 注册的时候将user数据写入redis中&#xff0c;key如下…...

个人网站可以做导航/广州竞价托管代运营

近日由于莫名原因导致我原来的2019.2的kali系统出现问题无法进入&#xff0c;当时快照又刚好被删除&#xff0c;于是重装了一个kali&#xff0c;并在此记录一下。 初步配置配置源 vi /etc/apt/sources.list # deb-src http://http.kali.org/kali kali-rolling main contrib no…...