xxl-job基本原理以及底层流程讲解

news2024/7/6 18:38:52

任务执行策略

请添加图片描述
请添加图片描述

任务阻塞策略

请添加图片描述

整体架构部署

请添加图片描述
这里主要讲解下每个模块的作用

  • 调度模块:负责管理调度信息,按照调度配置发出调度请求,自身不承担任何业务代码。调度系统于任务解耦,提高了系统可用性和稳定性,同时调度系统性能不受限于任务模块
  • 执行器:主要负责任务的具体执行,以及分配线程执行任务。同时执行器需要注册到调度中心,以及执行的回调结果需要通知调度器
部署注意事项
  • 调度中心和执行器都可以多节点部署,但是调度中心有一定注意事项
    • 连接同一个数据库(避免账号、任务、执行器等信息不同步)
    • 集群机器时钟保持一致(避免任务出现重复调度)

执行器和调度器底层原理

请添加图片描述

执行器的注册和发现方式

  • 执行器启动的时候,主动注册到调度中心,并且定时发送心跳,保持续约。执行器正常关闭时候,也主动告知调度中心注销掉
  • 如果执行器宕机或者其余问题,调度中心不知道执行器的情况,这时候就需要调度器不断对执行器进行探活,会有一个专门的后台线程,定时调用执行器接口,如果有异常就下线

执行器的启动

 //XxlJobSpringExecutor-》XxlJobExecutor-》start()
 public void start() throws Exception {

        // 初始化日志路线
        XxlJobFileAppender.initLogPath(logPath);

        // 创建调度器客户端,主要是执行器用于连接调度器的
        initAdminBizList(adminAddresses, accessToken);


        // 初始化日志清理线程
        JobLogFileCleanThread.getInstance().start(logRetentionDays);

        // 初始化回调线程
        TriggerCallbackThread.getInstance().start();

        // 初始化执行器服务器
        initEmbedServer(address, ip, port, appname, accessToken);
    }

initEmbedServer(初始化执行器服务器)

首先获取执行器端口和ip,然后启动一个Springboot内置的embedServer

	private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {

        // fill ip port
        port = port>0?port: NetUtil.findAvailablePort(9999);
        ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();

        // generate address
        if (address==null || address.trim().length()==0) {
            String ip_port_address = IpUtil.getIpPort(ip, port);   // registry-address:default use address to registry , otherwise use ip:port if address is null
            address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
        }

        // start
        embedServer = new EmbedServer();
        embedServer.start(address, port, appname, accessToken);
    }

embedServer.start

	//创建了一个业务线程池bizThreadPool
	//然后创建了一个Netty通信bootstrap,用于绑定端口启动
	//然后注册执行器startRegistry(appname, address);
	public void start(final String address, final int port, final String appname, final String accessToken) {
        executorBiz = new ExecutorBizImpl();
        thread = new Thread(new Runnable() {

            @Override
            public void run() {

                // param
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workerGroup = new NioEventLoopGroup();
                ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                        0,
                        200,
                        60L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(2000),
                        new ThreadFactory() {
                            @Override
                            public Thread newThread(Runnable r) {
                                return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode());
                            }
                        },
                        new RejectedExecutionHandler() {
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                                throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                            }
                        });


                try {
                    // start server
                    ServerBootstrap bootstrap = new ServerBootstrap();
                    bootstrap.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel channel) throws Exception {
                                    channel.pipeline()
                                            .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle
                                            .addLast(new HttpServerCodec())
                                            .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                                            .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                                }
                            })
                            .childOption(ChannelOption.SO_KEEPALIVE, true);

                    // bind
                    ChannelFuture future = bootstrap.bind(port).sync();

                    logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

                    // start registry
                    startRegistry(appname, address);

                    // wait util stop
                    future.channel().closeFuture().sync();

                } catch (InterruptedException e) {
                    if (e instanceof InterruptedException) {
                        logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                    } else {
                        logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
                    }
                } finally {
                    // stop
                    try {
                        workerGroup.shutdownGracefully();
                        bossGroup.shutdownGracefully();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }

            }

        });
        thread.setDaemon(true);	// daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
        thread.start();
    }

注册执行器

注册执行器,主要是这一个方法,通过http接口,进行注册

	 for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
       try {
            ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                registryResult = ReturnT.SUCCESS;
                logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                break;
            } else {
                logger.info(">>>>>>>>>>> xxl-job registry-remove fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
            }
        } catch (Exception e) {
            if (!toStop) {
                logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
            }

        }

    }

调度器接受到请求

然后到调度器模块JobApiController-》"registry"分支-》调用mapper方法插入数据库

 @Override
    public ReturnT<String> registry(RegistryParam registryParam) {

        // valid
        if (!StringUtils.hasText(registryParam.getRegistryGroup())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
        }

        int ret = xxlJobRegistryDao.registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
        if (ret < 1) {
            xxlJobRegistryDao.registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());

            // fresh
            freshGroupRegistryInfo(registryParam);
        }
        return ReturnT.SUCCESS;
    }

调度器启动

public void init() throws Exception {
        // init i18n
        initI18n();

        // 任务注册监听器,不停更新注册表,吧超时的执行器剔除,每30s一次
        JobRegistryMonitorHelper.getInstance().start();

        //任务调度失败监控器,失败重试、发送邮件等
        JobFailMonitorHelper.getInstance().start();

        // 任务结果丢失处理
        JobLosedMonitorHelper.getInstance().start();

        // 2个调度线程启动(一快一慢)
        JobTriggerPoolHelper.toStart();

        // 日志报告启动
        JobLogReportHelper.getInstance().start();

        // start-schedule
        JobScheduleHelper.getInstance().start();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }

JobScheduleHelper.getInstance().start();

  • 调度线程

    • 首先随机睡眠4-5s,有可能是防止执行器集中启动导致资源竞争问题
    • 然后计算预读取任务数,默认6000
    • 然后就是调度器重复执行的事情
      • 首先获取数据库的排他锁,因为之前说过调度器集群部署需要连接同一个数据库,这里也就是说明后面的操作是互斥的,并且多个调度器服务在同一时间只能有一个调度器获取任务信息
      • .获取锁成功以后,就执行查询任务逻辑(查询状态是1,并且5s之内需要触发的任务)
      • 然后这里将任务分为三种情况(没有达到触发时间,需要触发5s以内,已经过期5s以上的)
        • 过期5s以上的:这里就不做任何处理,只是更新下次调度时间
        • 正常执行的:正常执行任务、然后更新下一次触发时间、然后进行一次预读(提升性能,主要是判断触发时间是否满足,如果满足就丢入时间轮等待,然后更新下次触发时间)
        • 还没到时间的:丢入时间轮、刷新触发时间(实际上没用,因为时间还没到)
  • 时间轮线程

    • 初始化的时候先对齐秒数:主要是为了下一个整秒运行,因为时间轮的分割都是1s为维度
    • 然后进入循环,开始执行
    • 避免处理耗时太长,跨过刻度,向前校验一个刻度(也就是当前秒和前一秒的任务都取出来)
    • 然后就是触发任务了
  • 任务调度的流程

    • 还记得之前说的线程池有两个,一个快线程池、一个慢线程池吧,就是在这里使用,让任务执行时间比较久的进入慢线程池,避免阻塞后续任务,这里主要就是一个线程隔离(判断标准为:job-timeout 10 times in 1 min)
    • 然后就开始了任务执行的逻辑,首先拿到任务信息,如果参数的failRetryCount>0就用参数值,否则Job定义的
    • 然后拿到失败重试次数和组别
    • 如果是分片广播的话,会拿到对应的分片参数,然后吧这个参数分给对应的节点,然后根据实现的逻辑去执行对应的任务
    • 其余逻辑按照之前的路由规则拿到不同的执行器,然后通过http请求触发远程执行
  • 时间轮的作用

    • 其实时间轮的作用很简单,就是任务太多了,然后可以有一个规划来让任务进行时间分组,方便与顺序执行
      请添加图片描述
  • 执行器执行任务

    • 其实主要就是在之前创建的业务线程里面执行的
    • 先从容器中拿到任务线程(每个任务都有一个单独线程执行)
    • 然后再拿到对应的jobHandler,然后通过阻塞策略判断,如何执行这个任务
    • 然后通过任务线程吧这个Trigger加入队列,顺序执行,然后看是否有超时的配置,因为超时也就是通过Featuer的超时限制的
    • .然后在finally里面会进行任务的回调,然后也有对应的后台线程执行
    • 包括任务重试,也是有对应的后台线程依次执行
public void start(){
		
        
        scheduleThread = new Thread(new Runnable() {
            @Override
            public void run() {
		
                try {
                    TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                    if (!scheduleThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
                logger.info(">>>>>>>>> init xxl-job admin scheduler success.");

                // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
                int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;

                while (!scheduleThreadToStop) {

                    // Scan Job
                    long start = System.currentTimeMillis();

                    Connection conn = null;
                    Boolean connAutoCommit = null;
                    PreparedStatement preparedStatement = null;

                    boolean preReadSuc = true;
                    try {

                        conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
                        connAutoCommit = conn.getAutoCommit();
                        conn.setAutoCommit(false);

                        preparedStatement = conn.prepareStatement(  "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
                        preparedStatement.execute();

                        // tx start

                        // 1、pre read
                        long nowTime = System.currentTimeMillis();
                        List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
                        if (scheduleList!=null && scheduleList.size()>0) {
                            // 2、push time-ring
                            for (XxlJobInfo jobInfo: scheduleList) {

                                // time-ring jump
                                if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {
                                    // 2.1、trigger-expire > 5s:pass && make next-trigger-time
                                    logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId());

                                    // fresh next
                                    refreshNextValidTime(jobInfo, new Date());

                                } else if (nowTime > jobInfo.getTriggerNextTime()) {
                                    // 2.2、trigger-expire < 5s:direct-trigger && make next-trigger-time

                                    // 1、trigger
                                    JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
                                    logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() );

                                    // 2、fresh next
                                    refreshNextValidTime(jobInfo, new Date());

                                    // next-trigger-time in 5s, pre-read again
                                    if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {

                                        // 1、make ring second
                                        int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);

                                        // 2、push time ring
                                        pushTimeRing(ringSecond, jobInfo.getId());

                                        // 3、fresh next
                                        refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));

                                    }

                                } else {
                                    // 2.3、trigger-pre-read:time-ring trigger && make next-trigger-time

                                    // 1、make ring second
                                    int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);

                                    // 2、push time ring
                                    pushTimeRing(ringSecond, jobInfo.getId());

                                    // 3、fresh next
                                    refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));

                                }

                            }

                            // 3、update trigger info
                            for (XxlJobInfo jobInfo: scheduleList) {
                                XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
                            }

                        } else {
                            preReadSuc = false;
                        }

                        // tx stop


                    } catch (Exception e) {
                        if (!scheduleThreadToStop) {
                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e);
                        }
                    } finally {

                        // commit
                        if (conn != null) {
                            try {
                                conn.commit();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.setAutoCommit(connAutoCommit);
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                            try {
                                conn.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                        }

                        // close PreparedStatement
                        if (null != preparedStatement) {
                            try {
                                preparedStatement.close();
                            } catch (SQLException e) {
                                if (!scheduleThreadToStop) {
                                    logger.error(e.getMessage(), e);
                                }
                            }
                        }
                    }
                    long cost = System.currentTimeMillis()-start;


                    // Wait seconds, align second
                    if (cost < 1000) {  // scan-overtime, not wait
                        try {
                            // pre-read period: success > scan each second; fail > skip this period;
                            TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
                        } catch (InterruptedException e) {
                            if (!scheduleThreadToStop) {
                                logger.error(e.getMessage(), e);
                            }
                        }
                    }

                }

                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop");
            }
        });
        scheduleThread.setDaemon(true);
        scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
        scheduleThread.start();


        // 时间轮线程
        ringThread = new Thread(new Runnable() {
            @Override
            public void run() {

                // align second
                try {
                    TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                    if (!ringThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }

                while (!ringThreadToStop) {

                    try {
                        // second data
                        List<Integer> ringItemData = new ArrayList<>();
                        int nowSecond = Calendar.getInstance().get(Calendar.SECOND);   // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
                        for (int i = 0; i < 2; i++) {
                            List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
                            if (tmpData != null) {
                                ringItemData.addAll(tmpData);
                            }
                        }

                        // ring trigger
                        logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) );
                        if (ringItemData.size() > 0) {
                            // do trigger
                            for (int jobId: ringItemData) {
                                // do trigger
                                JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
                            }
                            // clear
                            ringItemData.clear();
                        }
                    } catch (Exception e) {
                        if (!ringThreadToStop) {
                            logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e);
                        }
                    }

                    // next second, align second
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis()%1000);
                    } catch (InterruptedException e) {
                        if (!ringThreadToStop) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop");
            }
        });
        ringThread.setDaemon(true);
        ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
        ringThread.start();
    }

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

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

相关文章

MFC工程的文件说明

工程创建 使用VS创建一个MFC的工程&#xff0c;这里不做说明 文件说明 使用VS创建好的MFC工程有如下文件&#xff1a; MFC全称Microsoft Foundation Classes&#xff0c;也就是微软基础类库;是VC的核心&#xff0c;是C与Windows API的结合&#xff0c;很彻底的用C封装了Win…

这个医生说的防疫措施,我挺认可的

上面那个语音是一个朋友发给我的&#xff0c;语音时间比较长&#xff0c;但是里面讲的很多内容我觉得挺不错的&#xff0c;现在疫情反复&#xff0c;我们会听到很多关于疫情的信息&#xff0c;有的人说奥密克戎感染性很强&#xff0c;之前专家说的无症状是骗人的&#xff0c;根…

元宇宙产业委联席秘书长叶毓睿:去中心化和去中介化的定义、区别,以及和元宇宙的关系

原创 Peter Ye 转自&#xff1a;乐生活与爱IT Plus 近日有个有关元宇宙的线上分享&#xff0c;有位名叫谢晓雪的听众提了一个我之前没思考过的问题&#xff1a;去中心化和去中介化的区别&#xff1f; 当时我回答了一部分&#xff0c;但主要是讲的之间的联系&#xff0c;区…

【年终总结】求职面试一定要扬长避短

时光荏苒&#xff0c;这周日就是元旦了&#xff0c;我也把年终总结提上了日程。 前言 今年的年终总结我打算多写几篇&#xff0c;每篇瞄准一个方向&#xff0c;写一些对大家有帮助、有启发的内容。 初步的想法会整理三篇&#xff1a; 第一篇分享求职面试的经验第二篇分享接私…

Mybaits(环境搭建和基本使用)

目录   一、什么是 Mybaits   二、配置环境     2.1 导入 MyBatis Framework     2.2 连接 MyBatis   三、增删改查功能     3.1 创建实体类     3.2 select     3.3 delete 和 update     3.4 insert   四、SQL 注入     4.1 什么是 SQL…

2022年终总结、展望2023

2022年终总结、展望2023前言一、2022年工作成绩二、2022年工作不足即如何改进三、可以传承的工作方法或者经验四、2023年工作目标 &#xff08;目标细化、可落地&#xff09;<font colorred> 1、薪资待遇2、云端高效的实时智能视频处理平台架构图3、 云端高效的实时智能视…

Redis从部署群集到ASK路由

目录 数据库简介 一、数据库分类 二、Redis重要特性 三、redis应用场景 安装redis redis基本命令 redis持久化 redis主从复制 redis集群 群集实施 配置节点发现 Redis Cluster 通讯流程 Redis Cluster手动分配槽位 Redis Cluster ASK路由介绍 模拟故障转移 自动搭建部署Redis C…

前缀.因式分解.求和 .C

前缀和:在输入同时获得结果. s[i]s[i-1]input(a[i]). 区间和:前缀做差 Sum[A,B]s[B]-s[A-1]. for(i1,i<n,i){ input(a[i]); s[i]s[i-1]input(a[i]). input(a,b); counts[b]-s[a-1]; } 分解伪代码 Sa2*a1a3*(a2a1)a4*(a3a2a1)a5*(a4a3a2a1) 因式分解复杂度on; >&g…

一文告诉你如何选择低代码供应商?

低代码&#xff08;零代码&#xff09;软件平台、套件、工具和相关服务正在快速地广泛普及和扩展。现在许多人都知道&#xff0c;低代码软件解决方案提供的加速器和自动化&#xff0c;可以加速软件应用程序开发人员的工作……这就意味着&#xff08;在这个开发人员匮乏的星球上…

动态规划算法典型例题

这里写目录标题1、动态规划算法2、动态规划&分治3、动态规划算法典型例题3.1选数问题3.1.1递归解法3.1.2动态规划解法3.2最长公共子序列3.3钢条切割问题3.3.1递归解法3.3.2动态规划解法3.4斐波那契数列3.4.1递归解法3.4.2递归解法3.5背包问题&#xff08;0-1背包&#xff0…

SRS服务器搭建以及展现配置说明

对于企业而言&#xff0c;数字化建设是一项全面的、系统的工程&#xff0c;不仅仅只是部署几套软件、实现办公自动化而已&#xff0c;尤其是大型企业&#xff0c;数字化的建设往往涉及到了服务器、硬件、软件、网络等一系列内容。如门禁系统和人力、认证等系统集成&#xff0c;…

计算机视觉与图形学-神经渲染专题-非刚体NeRF II

《TiNeuVox:Fast Dynamic Radiance Fields with Time-Aware Neural Voxel》链接&#xff1a;https://jaminfong.cn/tineuvox/摘要作者通过表示具有时间感知体素特征的场景提出了一个辐射场框架&#xff0c;并将其命名为 TiNeuVox。作者引入了一个微小的坐标变形网络来模拟粗略的…

Flink-使用合流操作进行实时对账需求的实现

学Flink第八章多流转换的时候&#xff0c;进行合流操作.connect()使用到了第九章状态编程的知识&#xff0c;感觉总体不是很清晰&#xff0c;因此学完状态编程后现在进行重温并细化一些细节 业务背景 步骤一&#xff1a; 用户进行支付的时候&#xff0c;后台是需要调用第三方…

leetcode 834. Sum of Distances in Tree(树中的距离和)

无向连接的树&#xff08;不一定是二叉树&#xff09;&#xff0c;求每个节点到其他节点的距离和。 返回一个数组&#xff0c;数组的第i个元素就是第i个节点到其他所有节点的距离之和。 思路&#xff1a; 涉及无向图的构造和遍历&#xff0c;树的前序后序遍历&#xff0c;问题…

论文复现-1:Perturbation CheckLists for Evaluating NLG Evaluation Metrics

以data2text任务为例&#xff0c;探讨generation metric矩阵对于一些句子扰动是否敏感&#xff0c;在多个维度上的敏感性如何&#xff1f; 1数据集 data2text数据集是由3025条samples构成&#xff0c;关键词由“ID”和“reference”构成。 每个子任务由对应的criteria&#…

python基础语法19-calendar模块

一、简介 有了time及datetime模块&#xff0c;再结合日历&#xff08;Calendar&#xff09;模块就可以更好的覆盖到时间处理的各个方面的应用。日历模块主要是用于处理日历及星期相关操作。 calendar模块的内置函数如下: 序号 函数及描述 1 calendar.calendar(yea…

Keras深度学习实战(42)——强化学习基础

Keras深度学习实战&#xff08;42&#xff09;——强化学习基础0. 前言1. 强化学习基础1.1 基本概念1.2 马尔科夫决策过程1.3 目标函数2. 在具有非负奖励的模拟游戏中获取最佳动作2.1 问题设定2.2 模型分析2.3 模型构建与训练3. 在模拟游戏中获取最佳动作3.1 问题定义3.2 模型分…

数据库原理及MySQL应用 | 数据表操作

数据表操作是数据库操作中最基本和最重要的操作。 图5-1是图书销售数据库booksale中存放的图书表books。 ■ 图5-1图书表books 01. 表的结构 表的结构也称为“型”(Type)&#xff0c;用于描述存储于表中的数据的逻辑结构和属性。定义表就是指定义表的结构&#xff0c;使用数据…

Vue CLI系列之生成打包报告

文章の目录一、通过命令行参数的形式生成报告二、通过可视化的UI面板直接查看报告写在最后打包时&#xff0c;为了直观地发现项目中存在的问题&#xff0c;可以在打包时生成报告。生成报告的方式有两种&#xff1a; 一、通过命令行参数的形式生成报告 "scripts": {…

关于对计算机发展史、冯诺依曼体系、CPU基本工作流程以及关于编程语言的简单认识

关于计算机发展史&#xff1a; 关于计算机发展史&#xff0c;大体经历了从一般计算工具到机械计算机到目前的电子计算机的发展历程。 公元前2500年&#xff0c;算盘已经出现&#xff1b; 1694 年&#xff0c;德国博物学家 戈特弗里德莱布尼兹建造了“步进计算器”。 关于步进…