Seata AT模式源码解析一(Seata Server端启动流程)

news2024/11/30 8:42:09

文章目录

  • 启动类 Server
    • SessionHolder初始化
    • DefaultCoordinator初始化
    • 初始化NettyRemotingServer

启动类 Server

seata-server的入口类在Server类中,源码如下:

public class Server {
    /**
    * The entry point of application.
    *
    * @param args the input arguments
    * @throws IOException the io exception
    */
    public static void main(String[] args) throws IOException {
        // 获取端口,默认是8091
        int port = PortHelper.getPort(args);
        System.setProperty(ConfigurationKeys.SERVER_PORT, Integer.toString(port));
        
        // create logger
        final Logger logger = LoggerFactory.getLogger(Server.class);
        if (ContainerHelper.isRunningInContainer()) {
            logger.info("The server is running in container.");
        }
        
        //参数解析器,用来解析启动的配置,包括file.conf和registry.conf
        //Note that the parameter parser should always be the first line to execute.
        //Because, here we need to parse the parameters needed for startup.
        ParameterParser parameterParser = new ParameterParser(args);
        
        //initialize the metrics
        MetricsManager.get().init();
        
        // 把从配置文件中读取到的storeMode写入SystemProperty中,方便其他类使用
        System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode());
        
        // netty的线程池
        ThreadPoolExecutor workingThreads = new ThreadPoolExecutor(NettyServerConfig.getMinServerPoolSize(),
                                                                   NettyServerConfig.getMaxServerPoolSize(), NettyServerConfig.getKeepAliveTime(), TimeUnit.SECONDS,
                                                                   new LinkedBlockingQueue<>(NettyServerConfig.getMaxTaskQueueSize()),
                                                                   new NamedThreadFactory("ServerHandlerThread", NettyServerConfig.getMaxServerPoolSize()), new ThreadPoolExecutor.CallerRunsPolicy());
        
        // 创建NettyRemotingServer实例,主要就是创建一个NettyServerBootstrap,负责与TM,RM进行通信
        NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads);
        // 监听的端口,8091
        nettyRemotingServer.setListenPort(parameterParser.getPort());
        // 初始化UUIDGenerator,UUID生成器,基于雪花算法,用于生成全局事务id,分支事务id
        // 多个Server实例配置不同的ServerNode,保证id的唯一性
        UUIDGenerator.init(parameterParser.getServerNode());
        // SessionHodler负责事务日志(状态)的持久化存储,
        // 根据不同的存储模式来创建
        SessionHolder.init(parameterParser.getStoreMode());
        
        // 创建DefaultCoordinator实例并初始化,DefaultCoordinator是TC的核心事务逻辑处理类
        DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);
        coordinator.init();
        // 将coordinator设置为事务消息处理器,处理netty接收到的事务请求
        nettyRemotingServer.setHandler(coordinator);
        // register ShutdownHook
        ShutdownHook.getInstance().addDisposable(coordinator);
        ShutdownHook.getInstance().addDisposable(nettyRemotingServer);
        
        //127.0.0.1 and 0.0.0.0 are not valid here.
        if (NetUtil.isValidIp(parameterParser.getHost(), false)) {
            XID.setIpAddress(parameterParser.getHost());
        } else {
            XID.setIpAddress(NetUtil.getLocalIp());
        }
        XID.setPort(nettyRemotingServer.getListenPort());
        
        try {
            // 初始化netty,开始监听端口并阻塞在这里
            nettyRemotingServer.init();
        } catch (Throwable e) {
            logger.error("nettyServer init error:{}", e.getMessage(), e);
            System.exit(-1);
        }
        
        System.exit(0);
    }
}

在阅读源码的时候,有些源码是要细看的,但是有些源码可以大致猜测一下它的作用,就直接略过去了,抓住真正的重点去看。

SessionHolder初始化

SessionHolder负责Session的持久化,一个session对象代表一个事务。SessionHolder包含了4个session管理器,用来操作session。

// 用于获取所有的session,以及session的创建,更新和删除
private static SessionManager ROOT_SESSION_MANAGER;
// 用于获取,更新所有异步commit的session
private static SessionManager ASYNC_COMMITTING_SESSION_MANAGER;
// 用于获取,更新所有需要重试commit的session
private static SessionManager RETRY_COMMITTING_SESSION_MANAGER;
// 用于获取,更新所有需要重试rollback的session
private static SessionManager RETRY_ROLLBACKING_SESSION_MANAGER;

初始化方法init

public static void init(String mode) {
    if (StringUtils.isBlank(mode)) {
        mode = CONFIG.getConfig(ConfigurationKeys.STORE_MODE);
    }
    StoreMode storeMode = StoreMode.get(mode);
    // 数据库存储模式,一般也是推荐用数据库
    if (StoreMode.DB.equals(storeMode)) {p
        // SPI方式加载SessionManager
        // 这里4个SessionManager都是DataBaseSessionManager类的4个不同实例
        ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName());
        ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                                                                      new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME}); // async.commit.data 表示是用来处理异步提交
        RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                                                                      new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME}); // retry.commit.data 表示是用来处理重试提交的
        RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.DB.getName(),
                                                                       new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME}); // retry.rollback.data 表示是用来处理重试回滚的
    } else if (StoreMode.FILE.equals(storeMode)) {
        String sessionStorePath = CONFIG.getConfig(ConfigurationKeys.STORE_FILE_DIR,
                                                   DEFAULT_SESSION_STORE_FILE_DIR);
        if (StringUtils.isBlank(sessionStorePath)) {
            throw new StoreException("the {store.file.dir} is empty.");
        }
        ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                                                          new Object[] {ROOT_SESSION_MANAGER_NAME, sessionStorePath});
        ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                                                                      new Class[] {String.class, String.class}, new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME, null});
        RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                                                                      new Class[] {String.class, String.class}, new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME, null});
        RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.FILE.getName(),
                                                                       new Class[] {String.class, String.class}, new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME, null});
    } else if (StoreMode.REDIS.equals(storeMode)) {
        ROOT_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class, StoreMode.REDIS.getName());
        ASYNC_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                                                                      StoreMode.REDIS.getName(), new Object[] {ASYNC_COMMITTING_SESSION_MANAGER_NAME});
        RETRY_COMMITTING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                                                                      StoreMode.REDIS.getName(), new Object[] {RETRY_COMMITTING_SESSION_MANAGER_NAME});
        RETRY_ROLLBACKING_SESSION_MANAGER = EnhancedServiceLoader.load(SessionManager.class,
                                                                       StoreMode.REDIS.getName(), new Object[] {RETRY_ROLLBACKING_SESSION_MANAGER_NAME});
    } else {
        // unknown store
        throw new IllegalArgumentException("unknown store mode:" + mode);
    }
    reload(storeMode);
}

DefaultCoordinator初始化

DefaultCoordinator是TC的核心事务逻辑处理类,如:开启、提交、回滚全局事务,注册、提交、回滚分支事务都是由DefaultCoordinator负责协调处理的。DefaultCoordinato通过RpcServer与远程的TM、RM通信来实现分支事务的提交、回滚等。

public DefaultCoordinator(RemotingServer remotingServer) {
    this.remotingServer = remotingServer;
    this.core = new DefaultCore(remotingServer);
}

在DefaultCoordinator里还创建了一个DefaultCore,该类是默认的 TC 事务操作实现,DefaultCoordinator的开启、提交、回滚全局事务,注册、提交、回滚分支事务都是委托给这个类。

public DefaultCore(RemotingServer remotingServer) {
    List<AbstractCore> allCore = EnhancedServiceLoader.loadAll(AbstractCore.class,
        new Class[]{RemotingServer.class}, new Object[]{remotingServer});
    if (CollectionUtils.isNotEmpty(allCore)) {
        for (AbstractCore core : allCore) {
            coreMap.put(core.getHandleBranchType(), core);
        }
    }
}

在DefaultCore构造方法里又会去通过SPI方式加载AbstractCore的实现类,类名在META-INF.services/io.seata.server.coordinator.AbstractCore文件里。
在这里插入图片描述
将这4个实例缓存在DefaultCore中的coreMap里,分别是AT,TCC,SAGA和XA模式下的事务处理类。

然后调用DefaultCoordinator的初始化方法init

public void init() {
    // 处理处于回滚状态可重试的事务的定时任务
    retryRollbacking.scheduleAtFixedRate(() -> {
        boolean lock = SessionHolder.retryRollbackingLock();
        if (lock) {
            try {
                handleRetryRollbacking();
            } catch (Exception e) {
                LOGGER.info("Exception retry rollbacking ... ", e);
            } finally {
                SessionHolder.unRetryRollbackingLock();
            }
        }
    }, 0, ROLLBACKING_RETRY_PERIOD, TimeUnit.MILLISECONDS);

    // 处理二阶段可以重试提交的状态可重试的事务的定时任务
    retryCommitting.scheduleAtFixedRate(() -> {
        boolean lock = SessionHolder.retryCommittingLock();
        if (lock) {
            try {
                handleRetryCommitting();
            } catch (Exception e) {
                LOGGER.info("Exception retry committing ... ", e);
            } finally {
                SessionHolder.unRetryCommittingLock();
            }
        }
    }, 0, COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);

    // 处理二阶段异步提交的事务的定时任务
    asyncCommitting.scheduleAtFixedRate(() -> {
        // 默认都是true
        boolean lock = SessionHolder.asyncCommittingLock();
        if (lock) {
            try {
                // 处理异步提交
                handleAsyncCommitting();
            } catch (Exception e) {
                LOGGER.info("Exception async committing ... ", e);
            } finally {
                SessionHolder.unAsyncCommittingLock();
            }
        }
    }, 0, ASYNC_COMMITTING_RETRY_PERIOD, TimeUnit.MILLISECONDS);

    // 检查事务的第一阶段已经超时的事务,设置事务状态为TimeoutRollbacking,
    // 该事务会由其他定时任务执行回滚操作
    timeoutCheck.scheduleAtFixedRate(() -> {
        boolean lock = SessionHolder.txTimeoutCheckLock();
        if (lock) {
            try {
                timeoutCheck();
            } catch (Exception e) {
                LOGGER.info("Exception timeout checking ... ", e);
            } finally {
                SessionHolder.unTxTimeoutCheckLock();
            }
        }
    }, 0, TIMEOUT_RETRY_PERIOD, TimeUnit.MILLISECONDS);

    // 根据unlog的保存天数调用RM删除unlog
    undoLogDelete.scheduleAtFixedRate(() -> {
        boolean lock = SessionHolder.undoLogDeleteLock();
        if (lock) {
            try {
                undoLogDelete();
            } catch (Exception e) {
                LOGGER.info("Exception undoLog deleting ... ", e);
            } finally {
                SessionHolder.unUndoLogDeleteLock();
            }
        }
    }, UNDO_LOG_DELAY_DELETE_PERIOD, UNDO_LOG_DELETE_PERIOD, TimeUnit.MILLISECONDS);
}

主要就是创建了5个定时任务,主要用于事务的重试机制,因为分布式环境的不稳定性会造成事务处于中间状态,所以要通过不断的重试机制来实现事务的最终一致性。这里面还有一个处理二阶段异步提交的事务的定时任务。

初始化NettyRemotingServer

在上面创建了NettyRemotingServer,所以在最后需要进行初始化,开始监听端口并阻塞在这里。

@Override
public void init() {
    // 注册与Client通信的Processor
    registerProcessor();
    // 再调用父类的init
    if (initialized.compareAndSet(false, true)) {
        super.init();
    }
}

NettyRemotingServer初始化时主要做了两件事:
1、注册与Client通信的Processor,每个事务请求类型都对应一个Processor。当NettyRemotingServer接收到请求后,从注册的Processor列表中选出一个适合的Processor进行处理。

private void registerProcessor() {
    // 1. 注册核心的ServerOnRequestProcessor,即与事务处理相关的Processor,
    // 如:全局事务开始、提交,分支事务注册、反馈当前状态等。
    // getHandler就是DefaultCoordinator
    ServerOnRequestProcessor onRequestProcessor =
        new ServerOnRequestProcessor(this, getHandler());
    super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS, onRequestProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_SEATA_MERGE, onRequestProcessor, messageExecutor);
    // 2. 注册ResponseProcessor,ResponseProcessor用于处理当Server端主动发起请求时,Client端回复的消息
    ServerOnResponseProcessor onResponseProcessor =
        new ServerOnResponseProcessor(getHandler(), getFutures());
    super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT_RESULT, onResponseProcessor, messageExecutor);
    super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK_RESULT, onResponseProcessor, messageExecutor);
    // 3. Client端发起RM注册请求时对应的Processor
    RegRmProcessor regRmProcessor = new RegRmProcessor(this);
    super.registerProcessor(MessageType.TYPE_REG_RM, regRmProcessor, messageExecutor);
    // 4. Client端发起TM注册请求时对应的Processor
    RegTmProcessor regTmProcessor = new RegTmProcessor(this);
    super.registerProcessor(MessageType.TYPE_REG_CLT, regTmProcessor, null);
    // 5. Client端发送心跳请求时对应的Processor
    ServerHeartbeatProcessor heartbeatMessageProcessor = new ServerHeartbeatProcessor(this);
    super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, heartbeatMessageProcessor, null);
}

2、调用父类AbstractNettyRemotingServer去启动Netty服务端

@Override
public void init() {
    super.init();
    // 启动Netty
    serverBootstrap.start();
}

继续调用父类AbstractNettyRemoting方法,创建一个定时任务。

public void init() {
    // 用于定时清除超时的请求,3s执行一次
    timerExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            for (Map.Entry<Integer, MessageFuture> entry : futures.entrySet()) {
                if (entry.getValue().isTimeout()) {
                    futures.remove(entry.getKey());
                    entry.getValue().setResultMessage(null);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("timeout clear future: {}", entry.getValue().getRequestMessage().getBody());
                    }
                }
            }

            nowMills = System.currentTimeMillis();
        }
    }, TIMEOUT_CHECK_INTERNAL, TIMEOUT_CHECK_INTERNAL, TimeUnit.MILLISECONDS);
}

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

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

相关文章

Intel Realsense D405 在Ubuntu18.04下使用kalibr标定

目录 一. 在ubuntu下使用kalibr标定realsense_ros的安装总结下面是标定步骤 一. 在ubuntu下使用kalibr标定 在Realsense官网上librealsense现在D405只接受ROS2下的环境&#xff08;相机确实很新&#xff09; 在ROS1下我想到了改设备ID号的方式进行标定 这里需要注意libreals…

蓝桥杯并查集总结

本文先是给出三篇并查集原理解释文章链接&#xff0c;又提供了python代码模版&#xff1b;而后给出了一份蓝桥杯并查集的题单&#xff0c;并附有部分题目及其求解思路、代码。 目录部分 并查集原理 python代码 并查集题单 蓝桥幼儿园 题目描述 输入描述 输出描述 输入…

ERP有哪些系统?运用在哪些行业?

国内目前市面上ERP系统五花八门&#xff0c;但能真正快速匹配企业业务&#xff0c;且可以进行快速迭代二次开发的系统并不多见。 所以在选择ERP系统的时候可以参考下面这张表格中的内容&#xff1a; 目前常见的ERP软件大概可以分为三大类&#xff1a; ① 标准ERP应用&#xf…

白银实时价格应该在最适合的地方下注

小时候我们看战争片&#xff0c;总是发现主角们带兵打仗&#xff0c;战无不胜&#xff0c;偶尔有一场大的失利&#xff0c;但是总是能耐化险为夷&#xff0c;逢凶化吉&#xff0c;甚至最后成功反扑、反败为胜。后来小编一琢磨&#xff0c;发现&#xff0c;其实这些将才们打仗&a…

如何使用C++ 在Word文档中创建列表

列表分类是指在Word文档中使用不同格式排序的列表&#xff0c;来帮助我们一目了然地表达出一段文字的主要内容。比如&#xff0c;当我们描述了某个主题的若干点&#xff0c;就可以用列表把它们一一表达出来&#xff0c;而不是写成完整的段落形式。同时&#xff0c;列表也可以帮…

如何查看mysql里面的锁(详细)

通过查询表统计信息查看 information_schema库下相关事务表和锁相关信息表介绍innodb_trx存储了当前正在执行的事务信息trx_id&#xff1a;事务ID。trx_state&#xff1a;事务状态&#xff0c;有以下几种状态&#xff1a;RUNNING、LOCK WAIT、ROLLING BACK 和 COMMITTING。trx…

各位自学网络安全的同学,你们的学习路线真的对吗

最近在知乎上看到很多问题&#xff0c;都是小白想要转行网络安全行业咨询学习路线和学习资料的&#xff0c;作为一个培训机构&#xff0c;学习路线和免费学习资料肯定是很多的。机构里面的不是顶级的黑阔大佬就是正在学习的同学&#xff0c;也用不上这些内容&#xff0c;每天都…

Educational Codeforces Round 139 (Rated for Div. 2)

Educational Codeforces Round 139 (Rated for Div. 2) Problem - 1766E - Codeforces 显然我们可以把0序列的贡献单独算: i*(n-i1) 考虑只存在1,2,3的情况. 首先通过&#xff0c;观察到一个重要性质: 最多只有三种序列. 含有3或纯1或纯2型.纯1或纯2型纯2或纯1型 我们每次添加…

照片资源异地共享 ? tftgallery、xampp、快解析三种工具就能实现!

我的工作中&#xff0c;经常会收到处理各种图片的任务&#xff0c;在处理完图片之后&#xff0c;怎么发送给客户呢&#xff1f;传输的实现&#xff0c;需要一个安全而稳定的环境和即时方便的工具去进行操作与下载。一般情况下&#xff0c;我们大多会选择微信、QQ来作为传输下载…

Seata AT模式源码解析三(AT模式工作机制)

文章目录 代码示例流程源码解析开启全局事务注册分支事务一阶段提交全局事务提交分支事务二阶段提交全局事务回滚分支事务二阶段回滚 代码示例 从一个微服务示例开始&#xff0c;案例采用Seata官方提供的Demo。 用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持&…

五大网络IO模型

网络IO模型 1. IO是什么&#xff1f; I/O&#xff08;英语&#xff1a;Input/Output&#xff09;&#xff0c;即输入&#xff0f;输出&#xff0c;通常指数据在存储器&#xff08;内部和外部&#xff09;或其他周边设备之间的输入和输出&#xff0c;是信息处理系统&#xff0…

Qt编程基础 | 第七章-MVD框架 | 7.1、MVD框架简介

一、MVD框架 1.1、MVC设计模式 MVC是一种与用户界面相关的设计模式&#xff0c;通过使用该设计模式&#xff0c;可以有效的分离数据与用户界面。MVC设计模式包含三个元素&#xff1a;表示数据的模型&#xff08;Model&#xff09;、表示用户界面的视图&#xff08;View&#x…

Android开发 外部存储空间

一、存储空间 对Andriod来说&#xff0c;存储空间分为内部存储空间和外部存储空间。 外部存储空间也就是常说的SD卡&#xff0c;可以分为私有存储空间和公共存储空间。 内部存储空间和外部存储空间的私有存储空间&#xff0c;都是每个APP独有的&#xff0c;不允许其他APP访问…

超详细的wireshark抓包使用教程

前言 Wireshark是非常流行的网络封包分析软件&#xff0c;可以截取各种网络数据包&#xff0c;并显示数据包详细信息。常用于开发测试过程各种问题定位。本文主要内容包括&#xff1a; 1、Wireshark软件下载和安装以及Wireshark主界面介绍。 2、WireShark简单抓包示例。通过该…

sql函数练习题

1. 计算用户8月每天的练题数量 题目&#xff1a;现在运营想要计算出2021年8月每天用户练习题目的数量&#xff0c;请取出相应数据。 示例&#xff1a;question_practice_detail iddevice_idquestion_idresultdate12138111wrong2021-05-0323214112wrong2021-05-0933214113wro…

2019上半年上午题

2019上半年上午题 a c c c d b b 应用代理网关防火墙 c a 使用数字证书对用户的身份进行认证 d 发送方的私钥签名&#xff0c;发送方公钥确认 d b 职务作品&#xff1a;归公司所有 a b b 从抽象到具体 d 等差数列求和&#xff1a; d 构建节点之间的关系图 然后…

4.Ansible Inventory介绍及实战 - A list or group of lists nodes

什么是inventory&#xff1f; 官方解释&#xff1a;Ansible automates tasks on managed nodes or “hosts” in your infrastructure, using a list or group of lists known as inventory. Ansible可以同时与您基础设施中的一个或多个系统协同工作&#xff61;为了与多台服务…

音视频学习:同步原理及实现

1.音视频同步简单介绍 对于一个播放器&#xff0c;一般来说&#xff0c;其基本构成均可划分为以下几部分&#xff1a; 数据接收&#xff08;网络/本地&#xff09;->解复用->音视频解码->音视频同步->音视频输出。 基本框架如下图所示&#xff1a; 为什么需要音视…

Python学习之批量word文档转pdf并统计其页码

pypdf2是一个Python模块&#xff0c;可以用来读取、写入和操作PDF文件。要安装pypdf2模块&#xff0c;请按照以下步骤操作&#xff1a; 确保你已经安装了Python。你可以在终端或命令提示符中输入python --version来检查Python是否已安装。 pypdf2模块的安装&#xff1a; Modu…

【2023年电工杯数学建模竞赛B题人工智能对大学生学习影响的评价】完整思路分析+完整代码

1.问题背景与描述 这道题整体还是相对简单的&#xff0c;比较适合新手&#xff0c;选的人多对应获奖数量也会多&#xff0c;所以不要纠结于选题&#xff0c;就选你看上去能做的就好 2.问题分析 2.1 问题一的分析 对附件2中所给数据进行分析和数值化处理&#xff0c;并给出处…