SpringCloud Alibaba Seata2.0分布式事务AT模式实践总结

这里我们划分订单、库存与支付三个module来实践Seata的分布式事务。

依赖版本(jdk17):

<spring.boot.version>3.1.7</spring.boot.version>
<spring.cloud.version>2022.0.4</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope>
</dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring.cloud.version}</version><type>pom</type><scope>import</scope>
</dependency>
<!--springcloud alibaba 2022.0.0.0-RC2-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring.cloud.alibaba.version}</version><type>pom</type><scope>import</scope>
</dependency>

【1】构建基础module

这里我们构建order、account和storage三个module,也就是演示订单的流程:创建订单、扣减库存、扣减账户余额。

Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。

整体module如下,module之间使用openfign进行调用。以订单模块为例进行说明,其他两个相似。

seata-account-service2003
seata-order-service2001
seata-storage-service2002

① pom依赖

<!-- nacos -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--alibaba-seata-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--openfeign-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--loadbalancer-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

② yml配置

server:port: 2001spring:application:name: seata-order-servicecloud:nacos:discovery:server-addr: localhost:8848         #Nacos服务注册中心地址# ==========applicationName + druid-mysql8 driver===================datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123456
# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.jane.cloud.entitiesconfiguration:map-underscore-to-camel-case: true# ========================seata===================
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称service:vgroup-mapping: # 点击源码分析default_tx_group: default # 事务组与TC服务集群的映射关系data-source-proxy-mode: ATlogging:level:io:seata: info

这里重点关注Seata的配置,相比1.0版本,Seata2.0版本无论是server端还是client端配置均进行了优化。

③ 创建undo_log表

undo_log建表、配置参数(仅AT模式)。SQL脚本 地址:https://github.com/apache/incubator-seata/blob/2.x/script/client/at/db/mysql.sql


-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',`xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context`       VARCHAR(128) 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 KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

最终数据库效果如下图:

在这里插入图片描述

【2】不添加@GlobalTransactional进行测试

如下所示,我们在订单处理方法里面进行远程调用扣减库存、扣减账户。service方法未添加@GlobalTransactional注解,也就是不启用分布式事务。

public void create(Order order){//xid全局事务id的检查,重要String xid = RootContext.getXID();//1 新建订单log.info("---------------开始新建订单: "+"\t"+"xid: "+xid);//订单新建时默认初始订单状态是零order.setStatus(0);int result = orderMapper.insertSelective(order);// 插入订单成功后获得插入mysql的实体对象Order orderFromDB = null;if(result > 0){// 从mysql里面查出刚插入的记录orderFromDB = orderMapper.selectOne(order);log.info("-----> 新建订单成功,orderFromDB info: "+orderFromDB);System.out.println();//2 扣减库存log.info("-------> 订单微服务开始调用Storage库存,做扣减count");storageFeignApi.decrease(orderFromDB.getProductId(),orderFromDB.getCount());log.info("-------> 订单微服务结束调用Storage库存,做扣减完成");System.out.println();//3 扣减账户余额log.info("-------> 订单微服务开始调用Account账号,做扣减money");accountFeignApi.decrease(orderFromDB.getUserId(),orderFromDB.getMoney());log.info("-------> 订单微服务结束调用Account账号,做扣减完成");System.out.println();//4 修改订单状态//将订单状态从零修改为1,表示已经完成log.info("-------> 修改订单状态");orderFromDB.setStatus(1);Example whereCondition = new Example(Order.class);Example.Criteria criteria = whereCondition.createCriteria();criteria.andEqualTo("userId",orderFromDB.getUserId());criteria.andEqualTo("status",0);int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);log.info("-------> 修改订单状态完成"+"\t"+updateResult);log.info("-------> orderFromDB info: "+orderFromDB);}System.out.println();log.info("---------------结束新建订单: "+"\t"+"xid: "+xid);}

那么假设account抛了异常,此时应该插入了订单并扣减了库存,但是账户余额没有扣除。我们修改AccountServiceImpl 使其超时:

public class AccountServiceImpl implements AccountService
{@ResourceAccountMapper accountMapper;/*** 扣减账户余额*/@Overridepublic void decrease(Long userId, Long money){log.info("------->account-service中扣减账户余额开始");accountMapper.decrease(userId,money);myTimeOut();log.info("------->account-service中扣减账户余额结束");}/*** 模拟超时异常,全局事务回滚*/private static void myTimeOut(){try { TimeUnit.SECONDS.sleep(65); } catch (InterruptedException e) { e.printStackTrace(); }}
}

此时我们查看数据库,可以验证上述推测是正确的:插入了订单并扣减了库存,但是账户余额没有扣除。其实这也就是我们为什么要引入分布式事务处理的原因:在分布式体系中,@Transactional 注解只能控制本地事务,不能控制其他服务事务,故而会引起分布式事务问题。

【3】添加@GlobalTransactional进行测试

如下所示,我们修改create方法如下:

# 添加全局事务注解
@GlobalTransactional(name = "create-order",rollbackFor = Exception.class) //AT
public void create(Order order)
{//xid全局事务id的检查,重要String xid = RootContext.getXID();//1 新建订单log.info("---------------开始新建订单: "+"\t"+"xid: "+xid);//订单新建时默认初始订单状态是零order.setStatus(0);//...
}

再次进行测试,仍然让account抛出异常。此时我们看控制台发现均已进行了回滚:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
数据库订单表、账户表以及库存表并没有产生脏数据,说明分布式事务生效。


我们再次测试,在account方法处打上断点,那么order模块会抛出超时异常:feign.RetryableException: Read timed out 。

这时TC就会让各个分支事务进行回滚,比如库存模块:

在这里插入图片描述

在Seata的控制台,我们可以看到目前只有account持有全局锁:

在这里插入图片描述

事务信息界面目前状态为RollbackRetrying,也就是重复尝试回滚(因为我们debug中)。

在这里插入图片描述
此时order分支二阶段回滚也已成功,但是全局事务回滚还未成功。

在这里插入图片描述

我们释放account的断点,使其二阶段回滚成功,此时全局事务也会回滚成功。

在这里插入图片描述


正常逻辑会有一个全局事务ID即XID和三个分支事务ID即branchID。XID和branchID是一对多的关系,表示分支事务在某个全局事务下。

在这里插入图片描述
在这里插入图片描述
当某个服务出现异常情况后,TM会告诉TC需要回滚,TC会通知各个分支事务进行回滚。回滚时会根据undo_log表的rollback_info信息还原数据行,并删除undo_log记录

2024-06-26T15:50:08.939+08:00  INFO 37872 --- [h_RMROLE_1_1_40] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=172.18.6.49:8091:3801574378475941899,branchId=3801574378475941900,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_order,applicationData={"skipCheckLock":true}
2024-06-26T15:50:08.942+08:00  INFO 37872 --- [h_RMROLE_1_1_40] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 172.18.6.49:8091:3801574378475941899 3801574378475941900 jdbc:mysql://localhost:3306/seata_order
2024-06-26T15:50:09.003+08:00  INFO 37872 --- [h_RMROLE_1_1_40] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.18.6.49:8091:3801574378475941899 branch 3801574378475941900, undo_log deleted with GlobalFinished
2024-06-26T15:50:09.005+08:00  INFO 37872 --- [h_RMROLE_1_1_40] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

整个事务回滚完毕后,状态会变为finished。

2024-06-26T15:50:09.224+08:00  INFO 37872 --- [nio-2001-exec-1] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 172.18.6.49:8091:3801574378475941899
2024-06-26T15:50:09.226+08:00  INFO 37872 --- [nio-2001-exec-1] i.seata.tm.api.DefaultGlobalTransaction  : [172.18.6.49:8091:3801574378475941899] rollback status: Finished

正常业务日志

① TM开启全局事务

order模块控制台

DefaultGlobalTransaction  : Begin new global transaction [172.18.6.49:8091:3801574378475941903]

② 服务处理

  • 下订单
  • 扣库存
  • 扣账户

③ TM决定通知TC提交全局事务

order模块控制台

DefaultGlobalTransaction  : Suspending current transaction, xid = 172.18.6.49:8091:3801574378475941903
DefaultGlobalTransaction  : [172.18.6.49:8091:3801574378475941903] commit status: Committed

此时TC会通知各个分支提交事务。

④ 各个分支提交事务

order模块控制台

2024-06-26T16:00:01.954+08:00  INFO 37872 --- [h_RMROLE_1_2_40] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.18.6.49:8091:3801574378475941903,branchId=3801574378475941904,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_order,applicationData={"skipCheckLock":true}
2024-06-26T16:00:01.955+08:00  INFO 37872 --- [h_RMROLE_1_2_40] io.seata.rm.AbstractRMHandler            : Branch committing: 172.18.6.49:8091:3801574378475941903 3801574378475941904 jdbc:mysql://localhost:3306/seata_order {"skipCheckLock":true}
2024-06-26T16:00:01.956+08:00  INFO 37872 --- [h_RMROLE_1_2_40] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

库存模块控制台

2024-06-26T16:00:01.965+08:00  INFO 40780 --- [h_RMROLE_1_2_40] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.18.6.49:8091:3801574378475941903,branchId=3801574378475941905,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_storage,applicationData=null
2024-06-26T16:00:01.967+08:00  INFO 40780 --- [h_RMROLE_1_2_40] io.seata.rm.AbstractRMHandler            : Branch committing: 172.18.6.49:8091:3801574378475941903 3801574378475941905 jdbc:mysql://localhost:3306/seata_storage null
2024-06-26T16:00:01.967+08:00  INFO 40780 --- [h_RMROLE_1_2_40] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

账户模块控制台:

2024-06-26T16:00:01.976+08:00  INFO 41368 --- [h_RMROLE_1_1_40] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.18.6.49:8091:3801574378475941903,branchId=3801574378475941906,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_account,applicationData=null
2024-06-26T16:00:01.980+08:00  INFO 41368 --- [h_RMROLE_1_1_40] io.seata.rm.AbstractRMHandler            : Branch committing: 172.18.6.49:8091:3801574378475941903 3801574378475941906 jdbc:mysql://localhost:3306/seata_account null
2024-06-26T16:00:01.982+08:00  INFO 41368 --- [h_RMROLE_1_1_40] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

order模块控制台(注意这个和第一次的区别):order模块是先{"skipCheckLock":true} 进行分支提交,等其他分支提交完后再进行applicationData=null分支提交。

2024-06-26T16:00:01.993+08:00  INFO 37872 --- [h_RMROLE_1_3_40] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.18.6.49:8091:3801574378475941903,branchId=3801574378475941907,branchType=AT,resourceId=jdbc:mysql://localhost:3306/seata_order,applicationData=null
2024-06-26T16:00:01.994+08:00  INFO 37872 --- [h_RMROLE_1_3_40] io.seata.rm.AbstractRMHandler            : Branch committing: 172.18.6.49:8091:3801574378475941903 3801574378475941907 jdbc:mysql://localhost:3306/seata_order null
2024-06-26T16:00:01.994+08:00  INFO 37872 --- [h_RMROLE_1_3_40] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

XID:172.18.6.49:8091:3801574378475941903
order分支ID:3801574378475941904、3801574378475941907
库存分支ID:3801574378475941905
账户分支ID:3801574378475941906

至此完成了分布式事务的提交。

【4】AT模式如何做到对业务的无侵入

官网地址:Seata 是什么? Seata默认是AT模式,两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

① 一阶段加载

在一阶段,Seata 会拦截“业务 SQL”,

  • 1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
  • 2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
  • 3 其保存成“after image”,最后生成行锁。

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
在这里插入图片描述

② 二阶段提交

二阶段如是顺利提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

在这里插入图片描述

③ 二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。

回滚方式便是用“before image”还原业务数据。但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”:

  • 如果两份数据完全一致就说明没有脏写,可以还原业务数据,
  • 如果不一致就说明有脏写,出现脏写就需要转人工处理。

在这里插入图片描述

【5】复习:@Transactional事务注解什么时候失效

@Transactional 注解可能会在以下几种情况下失效:

  1. 非public方法:当@Transactional注解应用在非public修饰的方法上时,事务管理将不会生效。这是因为在Spring AOP(面向切面编程)代理机制中,事务拦截器仅对public方法进行拦截处理。

  2. 自我调用:在一个类的内部,一个带有@Transactional注解的方法直接被另一个没有事务注解的方法调用时,事务可能不会生效。因为在这种情况下,调用是直接发生的,没有经过Spring的代理对象,从而不会触发事务管理。

  3. 未被Spring管理的类:如果带有@Transactional注解的类没有被Spring容器管理(例如,没有使用@Component、@Service等注解标记,或者没有在Spring配置中声明),事务注解将不起作用。

  4. 异常处理不当:如果在事务方法中捕获并吞没了应导致事务回滚的异常(通常是未检查异常,如RuntimeException),事务可能不会回滚。除非在@Transactional注解中明确指定了rollbackFor属性来捕获特定类型的异常。

  5. 代理模式问题:使用基于Java接口的代理时(Spring默认的代理方式),只有通过接口调用的方法才会被代理增强,如果直接在实现类上调用方法(而不是通过接口),事务可能不会生效。

  6. 事务传播行为设置不当:如果方法的事务传播行为配置不当(例如,一个事务方法调用另一个事务方法时,传播行为设置为PROPAGATION_NOT_SUPPORTEDPROPAGATION_NEVER),可能导致事务不生效或被挂起。

  7. 缺少数据库事务支持:如果底层数据访问技术不支持事务管理,如使用了非事务性的JDBC连接或某些NoSQL数据库操作,即使@Transactional注解正确使用,也无法实现事务管理。

要确保@Transactional注解能正常工作,需要确保遵循正确的使用方式,并注意上述可能引起事务失效的情况。

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

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

相关文章

简单说下GPT-4

ChatGPT 4.0&#xff0c;是OpenAI开发的基于GPT-4架构的大型语言模型。它在多个方面相较于前代版本有了显著的改进。以下是从专业角度对ChatGPT 4.0的详解&#xff1a; 架构与训练 1. **架构**&#xff1a;GPT-4采用的是变压器&#xff08;Transformer&#xff09;架构&#x…

计算机监控软件有哪些?10款常年霸榜的计算机监控软件

计算机监控软件是企业管理和保护信息安全的重要工具&#xff0c;它们帮助企业管理者监督员工的计算机使用行为&#xff0c;确保工作效率、数据安全以及合规性。在众多监控软件中&#xff0c;有些产品因其卓越的功能、易用性、安全性以及持续获得的良好市场反馈而常年占据行业领…

C++——探索智能指针的设计原理

前言: RAII是资源获得即初始化&#xff0c; 是一种利用对象生命周期来控制程序资源地手段。 智能指针是在对象构造时获取资源&#xff0c; 并且在对象的声明周期内控制资源&#xff0c; 最后在对象析构的时候释放资源。注意&#xff0c; 本篇文章参考——C 智能指针 - 全部用法…

IBCS 虚拟专线——让企业用于独立IP

在当今竞争激烈的商业世界中&#xff0c;企业的数字化运营对网络和服务器的性能有着极高的要求。作为一家企业的 IT 主管&#xff0c;我深刻体会到了在网络和服务器配置方面所面临的种种挑战&#xff0c;以及 IBCS 虚拟专线带来的革命性改变。 我们企业在业务扩张的过程中&…

msvcr120.dll文件下载的高级教程,修复msvcr120.dll 详细步骤分享

当电脑系统或特定应用程序无法找到或访问到msvcr120.dll文件时&#xff0c;便会导致错误消息的出现&#xff0c;例如“找不到 msvcr120.dll”、“msvcr120.dll丢失”等。这篇文章将大家讨论关于msvcr120.dll文件的内容、msvcr120.dll丢失问题的解决方法&#xff0c;其中最常见的…

Postman接口测试工具的原理及应用详解(一)

本系列文章简介&#xff1a; 在当今软件开发的世界中&#xff0c;接口测试作为保证软件质量的重要一环&#xff0c;其重要性不言而喻。随着前后端分离开发模式的普及&#xff0c;接口测试已成为连接前后端开发的桥梁&#xff0c;确保前后端之间的数据交互准确无误。在这样的背景…

web使用cordova打包Andriod

一.安装Gradel 1.下载地址 Gradle Distributions 2.配置环境 3.测试是否安装成功 在cmd gradle -v 二.创建vite项目 npm init vitelatest npm install vite build 三.创建cordova项目 1.全局安装cordova npm install -g cordova 2. 创建项目 cordova create cordova-app c…

【扩散模型应用方向】

参考&#xff1a;CVPR 2024 | 绝了&#xff01;&#xff01;最新 diffusion 扩散模型梳理&#xff01;100篇论文、40研究方向&#xff01; 扩散模型应用方向目录 1、扩散模型改进 2、可控文生图 3、风格迁移 4、人像生成 5、图像超分 6、图像恢复 7、目标跟踪 8、目标检…

VuePress日常使用

本篇来讲解下更多关于 VuePress 的基本用法 ‍ 配置首页 现在的页面太简单了&#xff0c;我们可以对项目首页进行配置&#xff0c;修改 docs/README.md &#xff08;这些配置是什么后面会说&#xff09;&#xff1a; --- home: true heroImage: https://s3.bmp.ovh/imgs/20…

Mac可以读取NTFS吗 Mac NTFS软件哪个好 mac ntfs读写工具免费

在跨操作系统环境下使用外部存储设备时&#xff0c;特别是当Windows系统的U盘被连接到Mac电脑时&#xff0c;常常会遇到文件系统兼容性的问题。由于Mac OS原生并不完全支持对NTFS格式磁盘的读写操作&#xff0c;导致用户无法直接在Mac上向NTFS格式的U盘或硬盘写入数据。下面我们…

Scala入门学习

Scala是一种现代化的多范式编程语言&#xff0c;它结合了面向对象编程和函数式编程的特点&#xff0c;运行在Java平台上。Scala的语法简洁&#xff0c;同时提供了强大的类型系统和丰富的库支持。下面是一些Scala入门的基础知识点&#xff1a; ### 1. 安装Scala - 下载并安装Sc…

详细分析JVM的底层原理及语法分析,JVM如何进行精细化的调优?小白进阶必读精品

Java虚拟机&#xff08;Java Virtual Machine&#xff0c;简称JVM&#xff09;是Java程序运行的基础&#xff0c;它提供了一个运行Java字节码的环境。JVM的底层原理涉及多方面的内容&#xff0c;包括类加载、字节码执行、内存管理、垃圾回收、即时编译&#xff08;JIT&#xff…

句柄数详解

目录 一. 什么是句柄二. 句柄书🟰文件打开数?三. 常用查看命令1. 查看当前系统中打开的文件数量(即文件句柄数)1)全局查看2)当前会话2. /proc/sys/fs/file-nr查看系统句柄数配置3. /proc/sys/fs/file-max查看系统支持的最大句柄数4. 显示当前会话中允许的最大句柄数量四…

揭示隐藏的模式:秩和检验和单因素方差分析的实战指南【考题】

1.研究一种新方法对于某实验结果准确性提高的效果&#xff0c;并将其与原有方法进行比较&#xff0c;结果见下表&#xff0c;请评价两者是否有不同? (行无序&#xff0c;列有序)-->单方向有序-->两独立样本的秩和检验) 如下图所示&#xff0c;先将相关数据导入spss。 图…

C# Modbus设备信息加载的实现方式(1)

Modbus设备信息加载的实现方式有很多种&#xff0c;这里讨论一种实现&#xff0c;思路比代码重要&#xff0c;具体类大家可以按自己的要求构建。 Load窗体事件中&#xff1a; GlobalProperties.Device LoadDevice(GroupPath, DevicePath, VariablePath); 具体的实现为&…

什么是指令微调(LLM)

经过大规模数据预训练后的语言模型已经具备较强的模型能力&#xff0c;能够编码丰富的世界知识&#xff0c;但是由于预训练任务形式所限&#xff0c;这些模型更擅长于文本补全&#xff0c;并不适合直接解决具体的任务。 指令微调是相对“预训练”来讲的&#xff0c;预训练的时…

SpringBoot3基础用法

技术和工具「!喜新厌旧」 一、背景 最近在一个轻量级的服务中&#xff0c;尝试了最新的技术和工具选型&#xff1b; 即SpringBoot3&#xff0c;JDK17&#xff0c;IDEA2023&#xff0c;Navicat16&#xff0c;虽然新的技术和工具都更加强大和高效&#xff0c;但是适应采坑的过程…

vue2 项目,一个方法还原data里所有的属性

在 Vue 2 中&#xff0c;如果你想要还原 data 对象里所有的属性到它们初始状态&#xff0c;可以利用 $options.data 方法来重新分配初始数据。以下是一种常见做法&#xff1a; methods: {resetData() {// 使用 $options.data 创建一个新的数据对象&#xff0c;这将会是组件初始…

简明system

参数详解 Unit模块 [Unit] 部分 Description&#xff1a;简短描述 Documentation&#xff1a;文档地址 Requires&#xff1a;当前 Unit 依赖的其他 Unit&#xff0c;如果它们没有运行&#xff0c;当前 Unit 会启动失败 Wants&#xff1a;与当前 Unit 配合的其他 Unit&#xf…

企业网络安全必知的三大访问控制模型

在当今信息化社会中&#xff0c;信息系统的安全性成为了组织和个人关注的焦点。随着信息技术的不断发展和应用&#xff0c;信息系统的复杂性和规模不断扩大&#xff0c;系统中存储和处理的信息量也日益增长。 一、引言 在当今信息化社会中&#xff0c;信息系统的安全性成为了组…