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

轻量级TCC框架的实现

现有seata、tcc-transaction等tcc框架实现都较为重量级,今天主要带来一种轻量级的实现,大概只用了1200行代码实现。不依赖具体框架grpc、http、dubbo等,只需要业务系统按照标准化实现Try、Commit、Cancel实现接口即可。

已解决悬挂、幂等、空回滚、事务嵌套问题,业务层面无需关注这部分处理。

TCC分为以下几个阶段:

  1. 执行前置动作 (业务资源的初始化,例如: 创建一个初始化的订单)
  2. Try (调用外部服务,进行资源的预留)
  3. 执行本地事务 (需要保证业务是在一个事务内完成)
  4. Commit\Cancel (根据本地事务的执行的成功与否,进行commit || cancel)

示例

该示例主要用于用户下单的同时,需要扣减用户积分的场景,订单服务和积分服务分别是独立服务部署,它们之间存在分布式事务的问题, 我们通过当前框架展示是如何解决以上问题的。

tcc/src/test/java/com/damon/sample at master · 654894017/tcc · GitHub

步骤1.初始化订单服务数据库表

-- 创建事务表
CREATE TABLE `tcc_main_log_order` (`biz_id` bigint NOT NULL COMMENT '业务id',`status` int NOT NULL DEFAULT '0' COMMENT '状态: 1 创建事务成功 2  回滚成功  3 完成本地事务成功  4 提交事务成功',`version` int NOT NULL DEFAULT '0' COMMENT '版本号',`last_update_time` bigint NOT NULL DEFAULT '0' COMMENT '最后更新时间',`create_time` bigint NOT NULL DEFAULT '0' COMMENT '创建时间',`checked_times` int NOT NULL DEFAULT '0' COMMENT '失败检查次数',PRIMARY KEY (`biz_id`),KEY `idx_status_checked_times_create_time` (`status`,`checked_times`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='主事务日志表';-- 创建订单表
CREATE TABLE `tcc_demo_order` (`order_id` bigint NOT NULL,`status` int NOT NULL,`user_id` bigint NOT NULL,`deduction_points` bigint DEFAULT NULL,PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

步骤2.积分服务创建子事务表

-- 创建子事务表
CREATE TABLE `tcc_sub_log_order` (`biz_id` bigint NOT NULL COMMENT '业务id',`sub_biz_id` bigint NOT NULL DEFAULT '0' COMMENT '子业务id',`status` int NOT NULL DEFAULT '0' COMMENT '状态: 1 创建事务成功 2  提交事务成功  3 回滚事务成功',`version` int NOT NULL DEFAULT '0' COMMENT '版本号',`last_update_time` bigint NOT NULL DEFAULT '0' COMMENT '最后更新时间',`create_time` bigint NOT NULL DEFAULT '0' COMMENT '创建时间',PRIMARY KEY (`biz_id`,`sub_biz_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='子事务日志表';-- 创建积分变动日志表
CREATE TABLE `tcc_demo_points_changing_log` (`biz_id` bigint NOT NULL,`user_id` bigint NOT NULL,`change_points` bigint NOT NULL,`change_type` int NOT NULL,`status` int NOT NULL,PRIMARY KEY (`biz_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;-- 创建用户积分表
CREATE TABLE `tcc_demo_user_points` (`user_id` bigint NOT NULL,`points` bigint NOT NULL,PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;-- 初始化用户积分
INSERT INTO `tcc_demo_user_points` (`user_id`, `points`) VALUES (12345678, 999999999989989999);

注意事项

事务表都是以tcc_main_log_xxxx 命名,子事务表都是以tcc_sub_log_xxxx命名,xxxx为业务分类,例如订单下单的业务,事务表命名为tcc_main_log_order, 子事务表命名为tcc_sub_log_order.

步骤3.运行 com.damon.sample.points.PointsApplication

步骤4.运行 com.damon.sample.order.TestRun

下单服务

下单服务继承TccMainService服务

package com.damon.sample.order.app;import cn.hutool.core.util.IdUtil;
import com.damon.sample.order.client.IOrderSubmitAppService;
import com.damon.sample.order.domain.IPointsGateway;
import com.damon.sample.order.domain.Order;
import com.damon.tcc.TccMainService;
import com.damon.tcc.config.TccMainConfig;
import com.damon.tcc.exception.TccLocalTransactionException;
import com.damon.tcc.exception.TccPrepareException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
public class OrderSubmitAppService extends TccMainService<Long, Map<String, Boolean>, Order> implements IOrderSubmitAppService {private final JdbcTemplate jdbcTemplate;private final IPointsGateway pointsGateway;@Autowiredpublic OrderSubmitAppService(TccMainConfig config, IPointsGateway pointsGateway) {super(config);this.jdbcTemplate = new JdbcTemplate(config.getDataSource());this.pointsGateway = pointsGateway;}/*** 检查失败的日志,用于纠正事务是否需要回顾还是提交*/public void executeFailedLogCheck() {super.executeFailedLogCheck();}/*** 检查死亡的日志,用于纠正事务是否需要回顾还是提交*/public void executeDeadLogCheck() {super.executeDeadLogCheck();}/*** 执行失败日志检查的时候需要回查请求参数(因为事务日志未记录方法请求参数,所以需要回查一下)** @param bizId 实体对象id(业务id)* @return*/@Overrideprotected Order callbackParameter(Long bizId) {return jdbcTemplate.queryForObject("select * from tcc_demo_order where order_id = ? ", new BeanPropertyRowMapper<>(Order.class), bizId);}/*** 创建订单 (1 预先创建订单  2 执行try动作)** @param userId* @param points* @return*/@Overridepublic Long submitOrder(Long userId, Long points) {Long orderId = IdUtil.getSnowflakeNextId();// 预创建订单jdbcTemplate.update("insert into tcc_demo_order(order_id, user_id, status, deduction_points) values (?, ?, ? ,? )",orderId, userId, 0, points);Order order = new Order(orderId, 0, userId, points);try {return super.process(order);} catch (TccPrepareException e) {throw e;} catch (TccLocalTransactionException e) {throw e;} catch (Exception e) {throw new RuntimeException("系统异常");}}/*** try执行用户积分扣除** @param order* @return*/@Overrideprotected Map<String, Boolean> prepare(Order order) {Boolean result = pointsGateway.tryDeductionPoints(order.getOrderId(), order.getUserId(), order.getDeductionPoints());Map<String, Boolean> map = new HashMap<>();map.put("flag", result);return map;}@Overrideprotected Long executeLocalTransaction(Order object, Map<String, Boolean> map) {int result = jdbcTemplate.update("update tcc_demo_order set status = ?  where order_id = ? ", 1, object.getOrderId());if (result == 0) {throw new RuntimeException("无效的订单id : " + object.getOrderId());}return object.getOrderId();}/*** commit积分** @param order*/@Overrideprotected void commit(Order order) {pointsGateway.commitDeductionPoints(order.getOrderId(), order.getUserId(), order.getDeductionPoints());}/*** cancel回滚积分** @param order*/@Overrideprotected void cancel(Order order) {pointsGateway.cancelDeductionPoints(order.getOrderId(), order.getUserId(), order.getDeductionPoints());}
}

积分服务

积分服务继承TccSubService服务

package com.damon.sample.points.app;import com.damon.sample.points.client.IPointsDeductionAppService;
import com.damon.sample.points.client.PointsDeductCmd;
import com.damon.tcc.config.TccSubConfig;
import com.damon.tcc.TccSubService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronizationManager;@Service
public class PointsDeductionAppService extends TccSubService<Boolean, PointsDeductCmd> implements IPointsDeductionAppService {private final Logger log = LoggerFactory.getLogger(PointsDeductionAppService.class);private final JdbcTemplate jdbcTemplate;@Autowiredpublic PointsDeductionAppService(TccSubConfig config) {super(config);this.jdbcTemplate = new JdbcTemplate(config.getDataSource());}/*** try执行积分扣减* @param parameter* @return*/@Overridepublic boolean attempt(PointsDeductCmd parameter) {return super.prepare(parameter, cmd -> {int result = jdbcTemplate.update("update tcc_demo_user_points set points = points - ? where user_id = ? and points - ? >= 0",cmd.getDeductionPoints(), cmd.getUserId(), cmd.getDeductionPoints());boolean transactionActive = TransactionSynchronizationManager.isActualTransactionActive();if (result == 0) {throw new RuntimeException("用户积分不足 || 用户不存在");}int result2 = jdbcTemplate.update("insert tcc_demo_points_changing_Log (user_id, change_points, change_type, biz_id, status) values(?,?,?,?,?)",cmd.getUserId(), cmd.getDeductionPoints(), 1, cmd.getOrderId(), 0);return true;});}/*** commit提交积分扣减* @param parameter*/@Overridepublic void commit(PointsDeductCmd parameter) {super.commit(parameter, cmd -> {int result = jdbcTemplate.update("update tcc_demo_points_changing_Log set status = 1 where biz_id = ?", cmd.getBizId());if (result == 0) {throw new RuntimeException("无效的业务id,无法积分commit");}});}/*** cancel回滚积分扣减* @param parameter*/@Overridepublic void cancel(PointsDeductCmd parameter) {super.cancel(parameter, cmd -> {int result = jdbcTemplate.update("update tcc_demo_points_changing_Log set status = 2 where biz_id = ?", cmd.getBizId());if (result == 0) {log.error("无效的业务id : {},无法进行积分cancel", cmd.getBizId());return;}int result2 = jdbcTemplate.update("update tcc_demo_user_points set points = points + ? where user_id = ?",cmd.getDeductionPoints(), cmd.getUserId());if (result2 == 0) {throw new RuntimeException("无效的用户id,无法进行积分rollback");}});}}

FAQ

1.关于幂等、悬挂、空回滚如何解决?

主要基于执行先判断的思路来实现的,在子系统执行Cancle的时候,都会先判断tcc_sub_log_xxxx这个表的事务事务已经完成或者取消,如果已完成直接不执行业务,如果未执行则执行Cancel业务,同时更新tcc_sub_log_xxxx的日志状态为已取消。最极端情况举一个示例,假如服务提供者try和cancel同时在执行(try因为网络问题非常滞后的到达业务服务器,这时主服务因为等待超时,调用了子服务cancel动作)。主要分两种情况:

1.假如先执行了cancel,在执行Try则不会执行,因为子事务已经取消(tcc_sub_log_xxxx会增加一条取消日志),在执行try操作时会出现索引冲突异常(tcc_sub_log_xxxx表有biz_id + sub_biz_id唯一索引),子事务的try会回滚。

2.假如先执行了try,在执行cancel则会执行正常取消,属于正常情况。还一种情况就是同时执行了Try、cancel操作,这时候只能依赖tcc_sub_log_xxxx表有biz_id + sub_biz_id唯一索引来解决更新冲突问题。假如Try先执行,cacel就会报错,上游服务重新发起cancel即可。假如先执行了cancle,则try会报错(唯一索引冲突),这时不用处理,子事务的try会回滚。

2.tcc_sub_log_xxxx表事务需要和本地业务在一个数据库事务?

是的,幂等、悬挂、空回滚问题都是基于tcc_sub_log_xxxx的事务日志表进行的,需要保证业务事务和tcc_sub_log_xxxx表的事务在一个数据库事务下。

3.上游系统重放try、commit、cancel怎么处理?

调用方误触发,存在以下几种可能

1.已经commit的事务,调用了cancle,已增加事务是否已commit判断,已commit的事务调用cancel不会执行,同时需要基于tcc_sub_log_xxxx表的version实现的乐观锁来解决更新冲突的问题。

2.已cancel的事务,调用了commit,已增加事务是否已cancel判断,已cancel的事务调用coommit不会执行,同时需要基于tcc_sub_log_xxxx表的version实现的乐观锁来解决更新冲突的问题。

3.已cancel的事务,调用了try,依赖tcc_sub_log_xxxx表biz_id + sub_biz_id唯一索引来解决更新冲突问题。

4.已commit的事务,调用了try,依赖tcc_sub_log_xxxx表biz_id + sub_biz_id唯一索引来解决更新冲突问题。

5.重复try依赖tcc_sub_log_xxxx表biz_id + sub_biz_id唯一索引来解决更新冲突问题。

6.重复commit,已增加事务是否已commit判断,已commit的事务调用commit不会执行,同时需要基于tcc_sub_log_xxxx表的version实现的乐观锁来解决更新冲突的问题。

7.重复cancel,已增加事务是否已cancel判断,已cancel的事务调用cancel不会执行,同时需要基于tcc_sub_log_xxxx表的version实现的乐观锁来解决更新冲突的问题。

GitHub - 654894017/tcc: 编程式tcc框架,已解决悬挂、幂等、空回滚、事务嵌套问题。

相关文章:

轻量级TCC框架的实现

现有seata、tcc-transaction等tcc框架实现都较为重量级&#xff0c;今天主要带来一种轻量级的实现&#xff0c;大概只用了1200行代码实现。不依赖具体框架grpc、http、dubbo等&#xff0c;只需要业务系统按照标准化实现Try、Commit、Cancel实现接口即可。 已解决悬挂、幂等、空…...

共绘智慧升级,看永洪科技助力由由集团起航智慧征途

在数字化洪流汹涌澎湃的当下&#xff0c;企业如何乘风破浪&#xff0c;把握转型升级的黄金机遇&#xff0c;已成为所有企业必须直面的时代命题。由由集团&#xff0c;作为房地产的领航者&#xff0c;始终以前瞻视野引领变革&#xff0c;坚决拥抱数字化浪潮&#xff0c;携手数字…...

小程序开发总结

今年第一次帮别人做小程序。 从开始动手到完成上线&#xff0c;一共耗时两天。AI 让写代码变得简单、高效。 不过&#xff0c;小程序和 Flutter 等大厂开发框架差距实在太大&#xff0c;导致我一开始根本找不到感觉。 第一&#xff0c;IDE 不好用&#xff0c;各种功能杂糅在…...

元脑服务器:浪潮信息引领AI基础设施的创新与发展

根据国际著名研究机构GlobalData于2月19日发布的最新报告&#xff0c;浪潮信息在全球数据中心领域的竞争力评估中表现出色&#xff0c;凭借其在算力算法、开放加速计算和液冷技术等方面的创新&#xff0c;获得了“Leader”评级。在创新、增长力与稳健性两个主要维度上&#xff…...

uniapp+node+mysql接入deepseek实现流式输出

node import express from express; import mysql from mysql2; import cors from cors; import bodyParser from body-parser; import axios from axios; import { WebSocketServer } from ws; // 正确导入 WebSocketServerconst app express();// Middlewares app.use(cors…...

PHP MySQL 创建数据库

PHP MySQL 创建数据库 引言 在网站开发中&#xff0c;数据库是存储和管理数据的核心部分。PHP 和 MySQL 是最常用的网页开发语言和数据库管理系统之一。本文将详细介绍如何在 PHP 中使用 MySQL 创建数据库&#xff0c;并对其操作进行详细讲解。 前提条件 在开始创建数据库之…...

UE4 World, Level, LevelStreaming从入门到深入

前言 在《塞尔达传说&#xff1a;旷野之息》中&#xff0c;玩家攀上初始高塔的瞬间&#xff0c;目光所及的山川湖泊皆可抵达&#xff1b;在《艾尔登法环》中&#xff0c;黄金树的辉光始终悬于地平线之上&#xff0c;指引玩家穿越无缝衔接的史诗战场。这些现代游戏杰作背后的核…...

3月8日实验

拓扑&#xff1a; 需求&#xff1a; 1.学校内部的HTTP客户端可以正常通过域名www.baidu.com访问到白度网络中的HTTP服务器 2.学校网络内部网段基于192.168.1.0/24划分&#xff0c;PC1可以正常访问3.3.3.0/24网段&#xff0c;但是PC2不允许 3.学校内部路由使用静态路由&#…...

IO多路复用实现并发服务器

一.select函数 select 的调用注意事项 在使用 select 函数时&#xff0c;需要注意以下几个关键点&#xff1a; 1. 参数的修改与拷贝 readfds 等参数是结果参数 &#xff1a; select 函数会直接修改传入的 fd_set&#xff08;如 readfds、writefds 和 exceptfds&#xf…...

【漫话机器学习系列】122.相关系数(Correlation Coefficient)

深入理解相关系数&#xff08;Correlation Coefficient&#xff09; 1. 引言 在数据分析、统计学和机器学习领域&#xff0c;研究变量之间的关系是至关重要的任务。我们常常想知道&#xff1a;当一个变量变化时&#xff0c;另一个变量是否也会随之变化&#xff1f;如果会&…...

控制系统分类

文章目录 定义与特点1. 自治系统&#xff08;Autonomous System&#xff09;与非自治系统&#xff08;Non-Autonomous System&#xff09;自治系统非自治系统 2. 线性系统&#xff08;Linear System&#xff09;与非线性系统&#xff08;Nonlinear System&#xff09;线性系统非…...

文档操作方法得合理使用

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…...

Python asyncIO 面试题及参考答案 草

目录 如何正确定义一个协程函数?直接调用协程会引发什么问题? 使用 async def 定义的协程与普通函数执行流程有何本质区别? 解释 asyncio.run () 的作用及与手动管理事件循环的差异 为什么协程中必须使用 await 而非 yield 挂起操作? 写出通过 async for 实现异步迭代器…...

计算机网络——交换机

一、什么是交换机&#xff1f; 交换机&#xff08;Switch&#xff09;是局域网&#xff08;LAN&#xff09;中的核心设备&#xff0c;负责在 数据链路层&#xff08;OSI第二层&#xff09;高效转发数据帧。它像一位“智能交通警察”&#xff0c;根据设备的 MAC地址 精准引导数…...

matlab和FPGA联合仿真时读写.txt文件数据的方法

在FPGA开发过程中&#xff0c;往往需要将MATLAB生成的数据作为原始激励灌入FPGA进行仿真。为了验证FPGA计算是否正确&#xff0c;又需要将FPGA计算结果导入MATLAB绘图与MATLAB计算结果对比。 下面是MATLAB“写.txt”、“读.txt”&#xff0c;Verilog“读.txt”、“写.txt”的代…...

解锁DeepSpeek-R1大模型微调:从训练到部署,打造定制化AI会话系统

目录 1. 前言 2.大模型微调概念简述 2.1. 按学习范式分类 2.2. 按参数更新范围分类 2.3. 大模型微调框架简介 3. DeepSpeek R1大模型微调实战 3.1.LLaMA-Factory基础环境安装 3.1大模型下载 3.2. 大模型训练 3.3. 大模型部署 3.4. 微调大模型融合基于SpirngBootVue2…...

【分布式】聊聊分布式id实现方案和生产经验

对于分布式Id来说&#xff0c;在面试过程中也是高频面试题&#xff0c;所以主要针对分布式id实现方案进行详细分析下。 应用场景 对于无论是单机还是分布式系统来说&#xff0c;对于很多场景需要全局唯一ID&#xff0c; 数据库id唯一性日志traceId 可以方便找到日志链&#…...

uniapp或者vue 使用serialport

参考https://blog.csdn.net/ykee126/article/details/90440499 版本是第一位&#xff1a;否则容易编译失败 node 版本 18.14.0 npm 版本 9.3.1 electron 版本 30.0.8 electron-rebuild 版本 3.2.9 serialport 版本 10.0.0 需要python环境 main.js // Modules to control app…...

机器学习12-视觉识别任务

机器学习12-视觉识别任务 分类语义分割滑动窗口滑动窗口的实现思路优点缺点现代替代方法 全卷积&#xff08;Fully Convolutional Networks, FCN&#xff09;FCN 的工作原理FCN 的性能优势FCN 的应用案例FCN 的局限性改进方向下采样可学习的上采样:转置卷积 目标检测区域建议Se…...

使用paramiko爆破ssh登录

一.确认是否存在目标主机是否存在root用户 重跑 CVE-2018-15473用户名枚举漏洞 检测&#xff1a; import paramiko from paramiko.ssh_exception import AuthenticationExceptiondef check_user(username, hostname, port):ssh paramiko.SSHClient()ssh.set_missing_host_key…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...