Spring 事务原理总结六

不知不觉,关于Spring事务的文章已经写了五篇了。老实讲我自己不断质疑过自己:现在写这些文章还有意义吗?当前的市场已经成什么样了,为什么还要固守这落后的技术?但是贝索斯一次接受访谈的回答,让我写下去的决心更加坚定了,他是这样说的:相较于千变万化的事物,我更关注那些恒久不变的东西!

书归正传,上篇文章我们解决了《Spring事务原理总结四》这篇文章中提到的几个问题中的三个,其中“Spring事务异常回滚执行流程”这个问题,我们并没有梳理。今天就借这篇文章详细梳理一下。如果各位觉得这些文章对您有用,还请多多关注,谢谢!如果大家觉得有哪些地方梳理的不正确,也请大家多多指教,非常感谢!本篇文章梳理的比较啰嗦,大家可以跳过中间过程,看最后的总结。如果有些地方大家觉得不对,欢迎指出。

执行流程梳理

继续采用《Spring事务原理总结一》中的案例,修改TransferServiceImpl类中的check(String, String, BigDecimal)方法,具体代码如下所示:

@Override
public void check(String from, String to, BigDecimal money) {System.out.println("校验开始");System.out.println("校验中||...........");try {System.out.println(1 / 0);Thread.sleep(1000 * 5);} catch (InterruptedException e) {}System.out.println("校验中==...........");System.out.println("校验结束");}

启动程序(用debug模式运行SpringTransactionApplication类即可),接着会在TransactionAspectSupport类的invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中的断点处停下,具体如下图所示:

启动程序(用debug模式运行SpringTransactionApplication类即可),接着会在TransactionAspectSupport类的invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation)方法中的断点处停下,具体如下图所示:

接着让我们继续执行代码,直到程序执行到下图所示的断点处停下,详情情况请参见下面这幅图:

这里需要注意一下,控制台并未输出TransferServiceImpl#check(String, String, BigDecimal)方法中要打印的任何内容,然后继续执行,结果如下图所示:

由图中可以看出,控制台输出了TransferServiceImpl#check(String, String, BigDecimal)方法要打印的内容,并且程序直接进入了catch逻辑。仔细观察会发现这个异常类型为java.lang.ArithmeticException: / by zero,这就是我们在代码中添加的System.out.println(1 / 0)抛出的,如果这个方法实际操作的是数据库,那catch逻辑中要执行的就是回滚操作了。先来看一下这个方法(这个方法——completeTransactionAfterThrowing(TransactionInfo, Throwable)——位于TransactionAspectSupport类中)的源码,如下所示:

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +"] after exception: " + ex);}if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {try {txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {logger.error("Application exception overridden by rollback exception", ex);ex2.initApplicationException(ex);throw ex2;}catch (RuntimeException | Error ex2) {logger.error("Application exception overridden by rollback exception", ex);throw ex2;}}else {// We don't roll back on this exception.// Will still roll back if TransactionStatus.isRollbackOnly() is true.try {txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {logger.error("Application exception overridden by commit exception", ex);ex2.initApplicationException(ex);throw ex2;}catch (RuntimeException | Error ex2) {logger.error("Application exception overridden by commit exception", ex);throw ex2;}}}
}

先来看一下下面这幅运行时图片,程序运行到if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex))处停下,具体见下图:

先让我们一起看一下txInfo.transactionAttribute.rollbackOn(ex)这句,这段代码的详细执行流程为:

  1. 执行DelegatingTransactionAttribute#rollbackOn(Throwable ex),注意DelegatingTransaction-Attribute中持有一个TransactionAttribute对象
  2. 调用RuleBasedTransactionAttribute#rollbackOn(Throwable ex)
  3. 调用DefaultTransactionAttribute#rollbackOn(Throwable ex)

对于这个调用过程,我们需要注意以下几点:

  • DelegatingTransactionAttribute类中持有的TransactionAttribute对象的实际类型是RuleBaseTransactionAttribute,这个类的rollbackOn(Throwable)方法实际上是一个代理方法,其会把处理转发给RuleBaseTransactionAttribute这个实际类型中的rollbackOn(Throwable)方法。所以DelegatingTransactionAttribute类中的rollbackOn(Throwable)方法的源码非常简单,具体如下所示:
public boolean rollbackOn(Throwable ex) {return this.targetAttribute.rollbackOn(ex);
}
  • RuleBasedTransactionAttribute这个类的rollbackOn(Throwable)方法是实际进行判断的地方,其中有一个rollbackRules对象,该对象的类型是List,其中存储的是一个一个的RollbackRuleAttribute类型的对象,首先来看一下rollbackOn(Throwable)这个方法的源码吧:
public boolean rollbackOn(Throwable ex) {RollbackRuleAttribute winner = null;int deepest = Integer.MAX_VALUE;if (this.rollbackRules != null) {for (RollbackRuleAttribute rule : this.rollbackRules) {int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}// User superclass behavior (rollback on unchecked) if no rule matches.if (winner == null) {return super.rollbackOn(ex);}return !(winner instanceof NoRollbackRuleAttribute);
}

通过这段源码不难发现,程序首先会遍历本类持有的rollbackRules对象,从中找到适合的数据并赋值给winner;接着判断winner对象是否为空,如果winner对象为空,则直接调用父类的rollbackOn(Throwable)方法,判断当前的异常类型是否合法,否则判断当前的winner对象是否为NoRollbackRuleAttribute类型,并取反,然后将结果返回给上级调用者。下面让我们看一下RollbackRuleAttribute类的继承结构,具体如下图所示:

接下来让我们看一下RuleBasedTransactionAttribute的父类DefaultTransactionAttribute类中的rollbackOn(Throwable)方法的源码

@Override
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}

这个方法的主要作用就是判断当前的异常类型是否为RuntimeException或Error,如果是这两个就返回true,否则就返回false

看到这里我不禁有个问题:RuleBasedTransactionAttribute对象中的rollbackRules对象中的值是从哪里来的?接着让我们改造以下TransferServiceImpl类上的@Transactional注解,在其中增加一个rollbackFor属性,具体如下图所示:

然后启动程序,查看结果(注意断点在RuleBasedTransactionAttribute#rollbackOn()方法中),具体如下图所示:

从图中不难发现此时rollbackRules对象中的值确实是我们通过rollbackFor属性注入的两个异常类。但我又有点好奇RuleBasedTransactionAttribute#rollbackOn(Throwable)方法中的最后一句“!(winner instanceof NoRollbackRuleAttribute)”中的NoRollbackRuleAttribute这个是从哪里来的?还记得@Transactional注解上的noRollbackFor属性吗?让我们继续改造TransferServiceImpl类上的@Transactional注解,在其上添加noRollbackFor属性,具体如下图所示:

然后重新启动程序,查看结果(注意断点在RuleBasedTransactionAttribute#rollbackOn()方法中),具体如下图所示:

从图中可以看出,我们在注解中指定的CustomException被包装成了NoRollbackRuleAttribute类型的对象,由于前面我们更改了TransferServiceImpl类的check()方法,其最终会抛出一个名为CustomException类型的异常,所以这段代码返回的结果是false,最终也就不会执行TransactionAspectSupport中的completeTransactionAfterThrowing(TransactionInfo, Throwable)方法。注意TransferServiceImpl类中的check()方法的源码为:

public void check(String from, String to, BigDecimal money) throws Exception {System.out.println("校验开始");System.out.println("校验中||...........");try {// System.out.println(1 / 0);Thread.sleep(1000 * 5);throw new CustomException();} catch (InterruptedException e) {}System.out.println("校验中==...........");System.out.println("校验结束");

抛开这些问题,继续回到DefaultTransactionAttribute#rollbackFor()方法中,最终程序抛出的ArithmeticException异常经过该方法后返回的结果为true。具体如下图所示:

总体来看在不指定@Transactional注解的rollbackFor属性的时候,调用RuleBasedTransact-ionAttribute#rollbackOn()方法的作用就是判断当前异常是否为运行时异常(即RuntimeException或者Error),如果是则触发后面的回滚逻辑,如果不是则不触发后面的回滚逻辑

接下来让我们继续回到TransactionAspectSupport#completeTransactionAfterThrowing(Tra-nsactionInfo, Throwable)方法中,经过前面的判断,最终代码走到了txInfo.getTransactionManag-er().rollback(txInfo.getTransactionStatus())这行,具体如下图所示:

首先来看一下TransactionManager类的rollback(TransactionStatus)方法的源码(实际代码位于AbstractPlatformTransactionManager类中):

public final void rollback(TransactionStatus status) throws TransactionException {if (status.isCompleted()) {throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;processRollback(defStatus, false);
}

接着再来看一下processRollback()方法的源码,其主要作用就是执行真正的回滚逻辑。下面一起看一下processRollback()方法的源码:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {try {boolean unexpectedRollback = unexpected;boolean rollbackListenerInvoked = false;try {triggerBeforeCompletion(status);if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Rolling back transaction to savepoint");}this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));rollbackListenerInvoked = true;status.rollbackToHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction rollback");}this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));rollbackListenerInvoked = true;doRollback(status);}else {// Participating in larger transactionif (status.hasTransaction()) {if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {if (status.isDebug()) {logger.debug("Participating transaction failed - marking existing transaction as rollback-only");}doSetRollbackOnly(status);}else {if (status.isDebug()) {logger.debug("Participating transaction failed - letting transaction originator decide on rollback");}}}else {logger.debug("Should roll back transaction but cannot - no transaction available");}// Unexpected rollback only matters here if we're asked to fail earlyif (!isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = false;}}}catch (RuntimeException | Error ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);if (rollbackListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex));}throw ex;}triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);if (rollbackListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));}// Raise UnexpectedRollbackException if we had a global rollback-only markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}
}

通过源码不难发现该方法接收一个TransactionStatus对象作为参数,这个对象封装了当前事务的状态信息。当需要回滚事务时(例如遇到未捕获异常或者显式调用TransactionTemplate或PlatformTransactionManager的rollback()方法时),框架会调用此方法来完成以下任务

  1. 清理资源:根据事务的具体类型(如JDBC、Hibernate、JTA等)清理与事务相关的一些资源,这可能包括数据库连接的回滚操作或者其他事务性资源的相应清理工作
  2. 更新事务状态:将事务状态标记为已回滚,确保后续不会尝试提交这个事务
  3. 触发监听器或回调:如果有注册的事务同步监听器(TransactionSynchronizationAdapter),则会触发相应的 afterCompletion 回调方法,通知它们事务已经回滚

总之,processRollback方法实现了事务生命周期中的“回滚”阶段,确保事务能够按照预期进行回滚,从而维持事务的原子性和一致性。通过debug跟踪和阅读源码,我们发现本示例最终走到了elsle if分支,该逻辑片段最终会调用DatasourceTransactionManager类的doRollback(DefaultTransactionStatus)方法,先来看一下这个方法的源码吧,具体如下所示:

protected void doRollback(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();if (status.isDebug()) {logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");}try {con.rollback();}catch (SQLException ex) {throw translateException("JDBC rollback", ex);}
}

通过源码,我们可以很清晰的看到,其最终就是通过调用Connection对象上的rollback()方法来完成事务的回滚的。

总结

文章进行到这里,前面遗留的问题基本上就梳理完了。首先通过这篇文章我们可以很清晰的看到Spring的设计者利用动态代理(cglib动态代理)及各种设计模式(责任链、模板等设计模式,其中模板设计模式在AbstractPlatformTransactionManager类体现的最为明显。该类中的rollback(TransactionStatus)方法规定了调用流程,即调用本类的processRollback(DefaultTransactionStatus, boolean)方法,而该方法又继续调用了本类中的模板方法doRollback(DefaultTransactionStatus),实际上调用的是其实现类中的方法完成了事务逻辑的抽离,使开发者可以花费更多的精力在业务代码的编写上。下面就让我们一起看一下AbstractPlatformTransactionManager类中的这几个方法的源码:

public final void rollback(TransactionStatus status) throws TransactionException {if (status.isCompleted()) {throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;processRollback(defStatus, false);
}
// 
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {try {boolean unexpectedRollback = unexpected;boolean rollbackListenerInvoked = false;try {triggerBeforeCompletion(status);if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Rolling back transaction to savepoint");}this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));rollbackListenerInvoked = true;status.rollbackToHeldSavepoint();}else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction rollback");}this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));rollbackListenerInvoked = true;doRollback(status);}else {// Participating in larger transactionif (status.hasTransaction()) {if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {if (status.isDebug()) {logger.debug("Participating transaction failed - marking existing transaction as rollback-only");}doSetRollbackOnly(status);}else {if (status.isDebug()) {logger.debug("Participating transaction failed - letting transaction originator decide on rollback");}}}else {logger.debug("Should roll back transaction but cannot - no transaction available");}// Unexpected rollback only matters here if we're asked to fail earlyif (!isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = false;}}}catch (RuntimeException | Error ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);if (rollbackListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex));}throw ex;}triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);if (rollbackListenerInvoked) {this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));}// Raise UnexpectedRollbackException if we had a global rollback-only markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}
}
// 
protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException;

下面再回顾一下AbstractPlatformTransactionManager的实现类DataSourceTransactionManager中的doRollback()方法的源码(如果向了解这两个类的继承关系,可以浏览《Spring 事务原理总结三》这篇文章)

protected void doRollback(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();Connection con = txObject.getConnectionHolder().getConnection();if (status.isDebug()) {logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");}try {con.rollback();}catch (SQLException ex) {throw translateException("JDBC rollback", ex);}
}

接着通过本章强化了我们对@Transactional注解的认识,该注解上的rollbackFor属性可以指定哪些异常发生时需要执行回滚操作,该注解上的noRollbackFor属性则可以指定哪些异常发生时不需要执行回滚操作(Spring设计者的这个设计,为开发者在开发中按照业务异常分别处理异常,提供了很大的便利)。另外通过本章我们也知道了通过这两个属性指定的异常最终会被分别包装成RollbackRuleAttribute和No RollbackRuleAttribute类型的对象,程序执行时会通过RuleBasedTransactionAttribute中的rollbackOn(Throwable ex)方法进行区分并加以判断,具体判断代码如下所示(含DelegatingTransactionAttribute和RuleBasedTransactionAttribute的父类DefaultTransactionAttribute中的rollbackOn(Throwable)方法):

/ DelegatingTransactionAttribute类中的rollbackOn(Throwable)方法
public boolean rollbackOn(Throwable ex) {return this.targetAttribute.rollbackOn(ex);
}
// RuleBasedTransactionAttribute类中的rollbackOn(Throwable)方法
public boolean rollbackOn(Throwable ex) {RollbackRuleAttribute winner = null;int deepest = Integer.MAX_VALUE;if (this.rollbackRules != null) {for (RollbackRuleAttribute rule : this.rollbackRules) {int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}// User superclass behavior (rollback on unchecked) if no rule matches.if (winner == null) {return super.rollbackOn(ex);}return !(winner instanceof NoRollbackRuleAttribute);
}
// RuleBasedTransactionAttribute的父类DefaultTransactionAttribute中的rollbackOn(Throwable)方法
@Override
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}

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

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

相关文章

ESP32-Cam学习(1)——拍摄第一张照片

1.开发板介绍 使用的ESP32-Cam实物图为&#xff1a; 在某宝可以轻易买到。它分为主板&#xff0c;和底板。底板的主要功能是供电、程序下载等等。主板才是ESP32芯片的核心。 2.固件烧录 使用摄像头之前&#xff0c;需要给ESP32刷入支持摄像头的固件库&#xff0c;其下载地址为…

数据库-----范式判断

目录 (1)求最小函数依赖集 (2)求候选码 (3)求R最高属于哪级范式 总结: 以一道例题来看: 3.已知关系模式R<ABCDEG> F{BC-->E&#xff0c;DC-->B,D-->A,B-->G,D-->E,E-->G,B-->C} 求: ①F的最小函数依赖集 ②R的候选码 ③R最高属于…

美国中性原子量子公司QuEra宣布将在英国建造量子测试平台

编辑丨慕一 编译/排版丨沛贤 深度好文&#xff1a;1250字丨7分钟阅读 中性原子量子公司QuEra Computing宣布&#xff0c;英国国家量子计算中心&#xff08;NQCC&#xff09;将成为一个量子计算测试平台的所在地。 通过NQCC的资助&#xff0c;并在小型企业研究计划&#xff…

【Kubernetes in Action笔记】1.快速开始

在Kubernetes上运行一个程序 基础运行环境 当前的运行环境为使用虚拟机构建的单master集群。 [rootk8s-master ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 109d v1.27.1 k8s-node1 Ready …

如何基于YAML设计接口自动化测试框架?看完秒会!

在设计自动化测试框架的时候&#xff0c;我们会经常将测试数据保存在外部的文件&#xff08;如Excel、YAML、CSV&#xff09;或者数据库中&#xff0c;实现脚本与数据解耦&#xff0c;方便后期维护。目前非常多的自动化测试框架采用通过Excel或者YAML文件直接编写测试用例&…

沁恒CH32V30X学习笔记05--串口接收中断和空闲中断组合接收数据

同步异步收发器(USART)** 包含 3 个通用同步异步收发器(USART1/2/3)和 5 个通用异步收发器(UART4/5/6/7/8) 空闲帧,空闲帧是 10 位或 11 位高电平,包含停止位。 断开帧是 10 位或 11 位低电平,后跟着停止位 引脚模式配置 引脚分配 bsp 驱动代码 bsp_uart_it.c /…

固定资产与总账对账,业务系统出不来数据?

1、【财务会计】-【固定资产】-【与总账对账】 2、【财务会计】-【总账】-【对账执行】 以上两个节点都可以进行 “固定资产与总账” 对账执行 操作。 问题&#xff1a; 固定资产与总账对账&#xff0c;业务系统出不来数据&#xff1f;如下图 &#xff1a; 原因&#xff…

麒麟linux和东方通TongWeb时区timezone不同步问题的解决

默认东方通文件夹位置如下&#xff1a; /data/TongWeb7.0.4.9_M3_Enterprise_Linux 在bin文件夹下有一个external.vmoptions 文件。 将下面这行&#xff1a; -Duser.timezoneAsia/Shanghai 添加到external.vmoptions 文件中。 重启东方通&#xff0c;时区问题解决。

VR直播:只需五步,即可实现直播“黑科技”

现如今&#xff0c;VR直播的应用范围较为广泛&#xff0c;有很多人可能在现场见过VR直播的拍摄设备&#xff0c;不仅有高性能的电脑、VR相机&#xff0c;还有专业的灯光和拍摄机器等。只需要五步&#xff0c;就可以实现安全、高效的VR全景直播。 首先是专业全景采集设备进行全景…

svg之全局组件,配合雪碧图解决vue2的svg优化问题

这里是vue2中的svg的完整解决方案的另一篇。 <template><svg :class"svgClass"><use :xlink:href"#${name}"></use></svg> </template><script>export default {name: icon,props: {name: {type: String,requi…

几种SLAM算法跑出的效果比较

以下所有的SLAM算法均使用此辆ROS小车跑。 文章目录 1.Gmapping SLAM算法构建地图2.Hector SLAM算法构建地图3.Karto SLAM算法构建地图4.Cartographer SLAM算法构建地图5.深度摄像头的建图6.rtab-map(深度双目与激光雷达构建三维建图)7.ORB-SLAM8.无奖竞猜 1.Gmapping SLAM算法…

OpenAI Sora视频生成机制:时空补丁

AI如何将静态图像转化为动态、逼真的视频&#xff1f;OpenAI 的 Sora 通过时空补丁&#xff08;spacetime patches&#xff09;的创新使用给出了答案。 独特的视频生成方法 在生成模型的世界中&#xff0c;我们看到了从 GAN 到自回归和扩散模型的许多方法&#xff0c;它们都有…

【C++初阶】值得一刷的字符串string相关oj题

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

【每天学习一点点 day04】工程化 npm create 脚手架 create-vue, vue-cli 执行原理① - npm cli

希望我们每个人都能找到属于自己的花期&#xff0c;不急不躁&#xff0c;静等风来。 今天打算用 Docusaurus 开始搭建自己的知识库&#xff0c;之前早已有此想法&#xff0c;遗憾的是没有坚持下来。 这次借助这个机会&#xff0c;也计划将自己【每天学习一点点】系列整理在自己…

java面试多线程篇

文章说明 在文档中对所有的面试题都进行了难易程度和出现频率的等级说明 星数越多代表权重越大&#xff0c;最多五颗星&#xff08;☆☆☆☆☆&#xff09; 最少一颗星&#xff08;☆&#xff09; 1.线程的基础知识 1.1 线程和进程的区别&#xff1f; 难易程度&#xff1a;☆☆…

代码随想录刷题笔记 DAY 29 | 非递减子序列 No.491 | 全排列 No.46 | 全排列 II No. 47

文章目录 Day 2901. 非递减子序列&#xff08;No. 491&#xff09;1.1 题目1.2 笔记1.3 代码 02. 全排列&#xff08;No. 46&#xff09;2.1 题目2.2 笔记2.3 代码 03. 全排列 II&#xff08;No. 47&#xff09;3.1 题目3.2 笔记3.3 代码 Day 29 01. 非递减子序列&#xff08;…

UEditorPlus v3.8.0 文档导入支持直接粘贴 Markdown 格式,已知问题修复

UEditor 是由百度开发的所见即所得的开源富文本编辑器&#xff0c;基于MIT开源协议&#xff0c;该富文本编辑器帮助不少网站开发者解决富文本编辑器的难点。 UEditorPlus 是有 ModStart 团队基于 UEditor 二次开发的富文本编辑器&#xff0c;主要做了样式的定制&#xff0c;更…

大模型LLM训练显存消耗详解

参考论文&#xff1a;ZeRO: Memory Optimizations Toward Training Trillion Parameter Models 大模型的显存消耗一直都是面试常见的问题&#xff0c;这次我就彻彻底底的根据论文ZeRO中的调研和分析做一次分析 显存消耗的两个部分&#xff1a;Model States&#xff08;跟模型的…

离线数仓(三)【业务日志采集平台搭建】

前言 上一篇我们搭建完了用户行为日志数据的采集平台&#xff0c;其实也就是用两个 flume 采集数据到Kafka 中&#xff08;这种结构只有 source 和 channel 没有 sink&#xff09; 。离线数仓中的数据除了用户日志&#xff0c;还有就是业务数据了。 1、电商业务简介 1.1 电商…

Mac软件打开提示:已损坏,无法打开。您应该将它移到废纸娄 怎么解决?

新入手的苹果电脑打开软件出现&#xff1a;“已损坏&#xff0c;无法打开。您应该将它移到废纸娄” 或 “已损坏&#xff0c;打不开。推出磁盘映像”。这个怎么解决&#xff1f; 第一部分&#xff1a;&#xff08;注意&#xff1a;任何来源打开过了的&#xff0c;就直接去看下…