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

分布式任务调度框架之开山鼻祖:Quartz

1.简介

最近我司上线使用了分布式任务调度框架:XXL-JOB,方便对任务的管理控制。本来一开始就想讲述一下该框架,但是在学习了解过程中发现该框架式基于Quartz思想开发实现的,Quartz 是一个很火的开源任务调度框架,完全由Java写成,可以说是 Java 定时任务领域的老大哥或者说参考标准,所以在这里先讲讲Quartz框架。

1.1.Quartz是什么

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。其功能类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能,作为一个优秀的开源调度框架,Quartz具有以下特点:

  • 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求
  • 灵活的应用方式,支持调度数据的多种存储方式
  • 分布式和集群能力

1.2.存储方式

RAMJobStore和JDBCJobStore
两者对比如下:

类型优点缺点
RAMJobStore不要外部数据库,配置容易,运行速度快因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBCJobStore支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务运行速度的快慢取决与连接数据库的快慢

根据上面知道,要想支持分布式集群,必须属于JDBCJobStore,其需要借助数据库MySQL,数据库初始化表SQL下载:tables,表描述说明如下:

表名说明
qrtz_blob_triggersTrigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
qrtz_calendars以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers存储Cron Trigger,包括Cron表达式和时区信息
qrtz_fired_triggers存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
qrtz_job_details存储每一个已配置的Job的详细信息
qrtz_locks存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps存储已暂停的Trigger组的信息
qrtz_scheduler_state存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
qrtz_triggers存储已配置的 Trigger的信息

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨群:Shepherd_126

2.springboot整合示例

springboot整合quartz非常简单,这里我们演示集群模式,所以使用JDBCJobStore,相关所需依赖如下:

        <!-- 实现对 Quartz 的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><!-- 实现对数据库连接池的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <!-- 本示例,我们使用 MySQL --><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency>

在创建任务之前,我们需要下载上面SQL语句执行一下,这里建表可以和业务数据库在同一个库,也可以单独放到一个数据库,如果单独建库建表,那么业务服务就是多数据源了,需要重新封装数据源连接。如下的多数据源配置:

@Configuration
public class DataSourceConfiguration {/*** 创建 user 数据源的配置对象*/@Primary@Bean(name = "userDataSourceProperties")@ConfigurationProperties(prefix = "spring.datasource.user") // 读取 spring.datasource.user 配置到 DataSourceProperties 对象public DataSourceProperties userDataSourceProperties() {return new DataSourceProperties();}/*** 创建 user 数据源*/@Primary@Bean(name = "userDataSource")@ConfigurationProperties(prefix = "spring.datasource.user.hikari") // 读取 spring.datasource.user 配置到 HikariDataSource 对象public DataSource userDataSource() {// 获得 DataSourceProperties 对象DataSourceProperties properties =  this.userDataSourceProperties();// 创建 HikariDataSource 对象return createHikariDataSource(properties);}/*** 创建 quartz 数据源的配置对象*/@Bean(name = "quartzDataSourceProperties")@ConfigurationProperties(prefix = "spring.datasource.quartz") // 读取 spring.datasource.quartz 配置到 DataSourceProperties 对象public DataSourceProperties quartzDataSourceProperties() {return new DataSourceProperties();}/*** 创建 quartz 数据源*/@Bean(name = "quartzDataSource")@ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")@QuartzDataSourcepublic DataSource quartzDataSource() {// 获得 DataSourceProperties 对象DataSourceProperties properties =  this.quartzDataSourceProperties();// 创建 HikariDataSource 对象return createHikariDataSource(properties);}private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {// 创建 HikariDataSource 对象HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();// 设置线程池名if (StringUtils.hasText(properties.getName())) {dataSource.setPoolName(properties.getName());}return dataSource;}}

为了快速简单测试,我们把Quartz的建表放到业务库一起,然后如下配置即可:

spring:datasource:url: jdbc:mysql://10.10.0.10:3306/ptc_job?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: root# Quartz 的配置,对应 QuartzProperties 配置类quartz:scheduler-name: clusteredScheduler # Scheduler 名字。默认为 schedulerNamejob-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。auto-startup: true # Quartz 是否自动启动startup-delay: 0 # 延迟 N 秒启动wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueoverwrite-existing-jobs: true # 是否覆盖已有 Job 的配置,注意为false时,修改已存在的任务调度cron,周期不生效jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档org:quartz:# JobStore 相关配置jobStore:# 数据源名称dataSource: quartzDataSource # 使用的数据源class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegatetablePrefix: QRTZ_ # Quartz 表前缀isClustered: true # 是集群模式clusterCheckinInterval: 1000useProperties: false# 线程池相关配置threadPool:threadCount: 25 # 线程池大小。默认为 10 。threadPriority: 5 # 线程优先级class: org.quartz.simpl.SimpleThreadPool # 线程池类型

创建任务Job1

@DisallowConcurrentExecution
public class Job1 extends QuartzJobBean {private Logger logger = LoggerFactory.getLogger(getClass());private static SimpleDateFormat fullDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");private final AtomicInteger count = new AtomicInteger();@Autowiredprivate DemoService demoService;private String k1;public void setK1(String k1) {this.k1 = k1;}@Overrideprotected void executeInternal(JobExecutionContext context) {logger.info("[job1的执行了,时间: {}, k1={}, count={}, demoService={}]", fullDateFormat.format(new Date()), k1,count.incrementAndGet(), demoService);}}

继承 QuartzJobBean 抽象类,实现 #executeInternal(JobExecutionContext context) 方法,执行自定义的定时任务的逻辑。

QuartzJobBean 实现了 org.quartz.Job 接口,提供了 Quartz 每次创建 Job 执行定时逻辑时,将该 JobDataMap数据进行依赖属性注入到Job Bean中。

// QuartzJobBean.javapublic final void execute(JobExecutionContext context) throws JobExecutionException {try {// 将当前对象,包装成 BeanWrapper 对象BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);// 设置属性到 bw 中MutablePropertyValues pvs = new MutablePropertyValues();pvs.addPropertyValues(context.getScheduler().getContext());pvs.addPropertyValues(context.getMergedJobDataMap());bw.setPropertyValues(pvs, true);} catch (SchedulerException ex) {throw new JobExecutionException(ex);}// 执行提供给子类实现的抽象方法this.executeInternal(context);
}protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;

注入Job任务配置如下:

        @Beanpublic JobDetail job1() {return JobBuilder.newJob(Job1.class).withIdentity("job1").storeDurably() .usingJobData("k1", "v1").build();}@Beanpublic Trigger simpleJobTrigger() {// 简单的调度计划的构造器SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(30) // 频率  30s执行一次。.repeatForever(); // 次数。// Trigger 构造器return TriggerBuilder.newTrigger().forJob(job1()) .withIdentity("job1Trigger") .withSchedule(scheduleBuilder) .build();}

这时候启动项目查看日志如下:

2022-09-20 23:17:33.500  INFO 18982 --- [eduler_Worker-2]  : [job1的执行了,时间: 2022-09-20 23:17:33, k1=v1, count=1, demoService=DemoService@3258ebff]
2022-09-20 23:18:03.463  INFO 18982 --- [eduler_Worker-3]  : [job1的执行了,时间: 2022-09-20 23:18:03, k1=v1, count=1, demoService=DemoService@3258ebff]
2022-09-20 23:18:33.439  INFO 18982 --- [eduler_Worker-4]  : [job1的执行了,时间: 2022-09-20 23:18:33, k1=v1, count=1, demoService=DemoService@3258ebff]
2022-09-20 23:19:03.448  INFO 18982 --- [eduler_Worker-5]  : [job1的执行了,时间: 2022-09-20 23:19:03, k1=v1, count=1, demoService=DemoService@3258ebff]

从计数器count可以看出,每次 Job0 都会被 Quartz 创建出一个新的 Job 对象,执行任务,但是DemoService属性值相同,是Spring单例bean,同时JobData的数据自动映射注入到任务bean属性上。

上面是通过简单调度器simpleSchedule指定频率执行任务,当然我也可以使用主流的基于cron表达式实现任务周期执行:

       @Beanpublic JobDetail job1() {return JobBuilder.newJob(Job1.class).withIdentity("job1").storeDurably() .usingJobData("k1", "v1").build();}@Beanpublic Trigger cronJobTrigger() {// 每隔1分钟执行一次CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 0/1 * * * ? *");// Trigger 构造器return TriggerBuilder.newTrigger().forJob(job1()) .withIdentity("job1Trigger") .withSchedule(scheduleBuilder) .build();}

任务调度执行结果这里就不再展示了,和上面一回事。

3.实现原理

Quartz 是通过 Scheduler 调度器来进行任务的操作,它可以把任务 JobDetail 和触发器 Trigger 加入任务池中,可以把任务删除,也可以把任务停止,scheduler 把这些任务和触发器放到一个 JobStore 中,这里 jobStore 有内存形式的也有持久化形式的,当然也可以自定义扩展成独立的服务。

Quartz内部会通过一个调度线程 QuartzSchedulerThread 不断到 JobStore 中找出下次需要执行的任务,并把这些任务封装放到一个线程池 ThreadPool 中运行,组件结构如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MTzKZywl-1691461202634)(https://markdown-file-zfj.oss-cn-hangzhou.aliyuncs.com/Quartz%E6%9E%B6%E6%9E%84%E5%9B%BE.png)]

核心类

QuartzSchedulerThread:负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。
JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。
JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。
Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context)
(jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中)

Job有个子接口StatefulJob ,代表有状态任务。有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。
下面展示原生的Quartz创建任务、绑定触发器、注册任务和定时器、启动调度器,

/*** 原生创建任务流程示例,有助于分析quartz实现原理* @throws SchedulerException*/public static void test() throws SchedulerException {//1.创建Scheduler的工厂SchedulerFactory sf = new StdSchedulerFactory();//2.从工厂中获取调度器实例Scheduler scheduler = sf.getScheduler();//3.创建JobDetailJobDetail jb = JobBuilder.newJob(Job1.class).withDescription("this is a job") //job的描述.withIdentity("job1", "test-job") //job 的name和group.build();//任务运行的时间,SimpleSchedule类型触发器有效long time=  System.currentTimeMillis() + 3*1000L; //3秒后启动任务Date statTime = new Date(time);//4.创建Trigger//使用SimpleScheduleBuilder或者CronScheduleBuilderTrigger t = TriggerBuilder.newTrigger().withDescription("").withIdentity("job1Trigger", "job1TriggerGroup")//.withSchedule(SimpleScheduleBuilder.simpleSchedule()).startAt(statTime)  //默认当前时间启动.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) //10秒执行一次.build();//5.注册任务和定时器scheduler.scheduleJob(jb, t);//源码分析//6.启动 调度器scheduler.start();}public static void main(String[] args) throws SchedulerException {test();}

接下来对主要三个步骤:创建调度器、注册任务和触发器、启动调度器执行任务进行分析

调度器初始化

 SchedulerFactory sf = new StdSchedulerFactory();Scheduler scheduler = sf.getScheduler();

SchedulerFacotory 是创建调度器的工厂接口,它有两个实现,StdSchedulerFacotory 根据配置文件来创建 Scheduler,DirectSchedulerFactory 主要通过编码对 Scheduler 控制,通常为了侵入性更小、实现更方便我们用 StdSchedulerFacotory 类型来创建 StdScheduler,quartz.properties 里面的配置都对应到这个 StdSchedulerFactory 中,所以对某个配置不明白已经该配置的默认值可以看 StdSchedulerFactory 中获取配置的代码。

sf.getScheduler()入手,进入StdSchedulerFacotory可以看到该方法逻辑:

public Scheduler getScheduler() throws SchedulerException {// 第一步:加载配置文件,System的properties覆盖前面的配置if (cfg == null) {initialize();}SchedulerRepository schedRep = SchedulerRepository.getInstance();Scheduler sched = schedRep.lookup(getSchedulerName());if (sched != null) {if (sched.isShutdown()) {schedRep.remove(getSchedulerName());} else {return sched;}}// 第二步:初始化,生成schedulersched = instantiate();return sched;}

这里一共完成两个逻辑:加载配置和生成scheduler,接下来进入核心方法instantiate(),这里面逻辑很多,其核心操作就是初始化各种调度所需要的对象,比如线程池、JobStore等等,最后把上面创建的对象放到 QuartzSchedulerResources 中并把线程池起来,这个相当于 QuartzScheduler 的资源存放处, 方法相关代码如下:

private Scheduler instantiate() throws SchedulerException{......// 要初始化的对象JobStore js = null;ThreadPool tp = null;QuartzScheduler qs = null;DBConnectionManager dbMgr = null;String instanceIdGeneratorClass = null;Properties tProps = null;String userTXLocation = null;boolean wrapJobInTx = false;boolean autoId = false;long idleWaitTime = -1;long dbFailureRetry = 15000L; // 15 secsString classLoadHelperClass;String jobFactoryClass;ThreadExecutor threadExecutor;.....QuartzSchedulerResources rsrcs = new QuartzSchedulerResources();rsrcs.setName(schedName);rsrcs.setThreadName(threadName);rsrcs.setInstanceId(schedInstId);rsrcs.setJobRunShellFactory(jrsf);rsrcs.setMakeSchedulerThreadDaemon(makeSchedulerThreadDaemon);rsrcs.setThreadsInheritInitializersClassLoadContext(threadsInheritInitalizersClassLoader);rsrcs.setRunUpdateCheck(!skipUpdateCheck);rsrcs.setBatchTimeWindow(batchTimeWindow);rsrcs.setMaxBatchSize(maxBatchSize);rsrcs.setInterruptJobsOnShutdown(interruptJobsOnShutdown);rsrcs.setInterruptJobsOnShutdownWithWait(interruptJobsOnShutdownWithWait);rsrcs.setJMXExport(jmxExport);rsrcs.setJMXObjectName(jmxObjectName);//这个线程执行者用于后面启动调度线程rsrcs.setThreadExecutor(threadExecutor);threadExecutor.initialize();rsrcs.setThreadPool(tp);if (tp instanceof SimpleThreadPool) {if (threadsInheritInitalizersClassLoader)((SimpleThreadPool) tp).setThreadsInheritContextClassLoaderOfInitializingThread(threadsInheritInitalizersClassLoader);}//执行线程池启动tp.initialize();tpInited = true;rsrcs.setJobStore(js);// add pluginsfor (int i = 0; i < plugins.length; i++) {rsrcs.addSchedulerPlugin(plugins[i]);}//调度线程在构造方法里面启动的qs = new QuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);}

经过上面调度器scheduler就初始化好了,接下来就可以定义Job和Trigger,然后通过scheduler.scheduleJob(jb, t)注册任务和触发器。

注册任务和触发器

scheduler.scheduleJob(jb, t)

进入StdScheduler#scheduleJob(JobDetail jobDetail, Trigger trigger)

   public Date scheduleJob(JobDetail jobDetail, Trigger trigger)throws SchedulerException {return sched.scheduleJob(jobDetail, trigger);}

这里的sched对象就是QuartzScheduler,进入sched.scheduleJob(jobDetail, trigger),这里就是注册任务和定时任务的核心逻辑。

 public Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException {.....//核心代码:存储给定的org.quartz.JobDetail和org.quartz.Trigger。resources.getJobStore().storeJobAndTrigger(jobDetail, trig);notifySchedulerListenersJobAdded(jobDetail);notifySchedulerThread(trigger.getNextFireTime().getTime());notifySchedulerListenersSchduled(trigger);return ft;}

这里的resources就是上面创建调度器scheduler时初始化各种对象然后放到资源管理处QuartzSchedulerResources,其里面包含对JobStore对象,然后再通过这个对象保存任务和触发器,至于保存逻辑的细节这里不在详述,请自行查看,反正核心逻辑这里上下文都对上了。

启动调度器执行任务

quartz 用一个线程不断轮询查找下次待执行的任务,并把任务交给线程池执行,这里涉及两种角色:调度线程和执行线程池。

scheduler.start();

scheduler.start() 调用 QuartzScheduler.start(),Quartz 的启动要调用start()方法进行线程的启动,线程中启动线程是调用start()方法,但是真正执行线程任务的操作在run()中

QuartzScheduler.start()代码如下:

public void start() throws SchedulerException {if (shuttingDown|| closed) {throw new SchedulerException("The Scheduler cannot be restarted after shutdown() has been called.");}notifySchedulerListenersStarting();if (initialStart == null) {//初始化标识为null,进行初始化操作initialStart = new Date();this.resources.getJobStore().schedulerStarted();//1 主要分析的地方      startPlugins();} else {resources.getJobStore().schedulerResumed();//2 如果已经初始化过,则恢复jobStore}schedThread.togglePause(false);//3 唤醒所有等待的线程getLog().info("Scheduler " + resources.getUniqueIdentifier() + " started.");notifySchedulerListenersStarted();
}

this.resources.getJobStore().schedulerStarted() ;主要分析的地方,实际上是调用 QuartzSchedulerResources中的JobStore进行启动。

最后QuartzSchedulerThread.run()主要是在有可用线程的时候获取需要执行Trigger并出触发进行任务的调度!

看线程 QuartzSchedulerThread 的 run () 方法以 while (true) 的方式循环执行,不断从jobStore中获取下次要触发的触发器集合,将任务放到线程池中执行,这也是Quartz实现定时周期执行任务的核心所在,具体分析请看:https://my.oschina.net/chengxiaoyuan/blog/674603

相关文章:

分布式任务调度框架之开山鼻祖:Quartz

1.简介 最近我司上线使用了分布式任务调度框架&#xff1a;XXL-JOB&#xff0c;方便对任务的管理控制。本来一开始就想讲述一下该框架&#xff0c;但是在学习了解过程中发现该框架式基于Quartz思想开发实现的&#xff0c;Quartz 是一个很火的开源任务调度框架&#xff0c;完全…...

Django实现音乐网站 ⑺

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是后台对歌手原有实现功能的基础上进行优化处理。 目录 新增编辑 表字段名称修改 隐藏单曲、专辑数 姓名首字母 安装xpinyin 获取姓名首字母 重写保存方法 列表显示 图片显示处理 引入函数 路径改为显示…...

PO、DTO和VO的区别

PO&#xff1a;Persistence Object&#xff0c;是持久化对象&#xff0c;也叫实体对象&#xff08;Entity&#xff09;&#xff0c;是对象到关系表的映射。在关系型数据库中&#xff0c;每张表对应着一个PO&#xff0c;PO 中的属性值与表中数据一一对应&#xff0c;通过数据访问…...

ReadableStream流的js处理

需求 今天接了一个gpt的项目。需要对接接口。后端返回的数据是ReadableStream类型的。记录下如何处理ReadableStream类型的数据 代码 const response ... // response 是服务端返回的数据// 创建了一个数据读取器 const reader response.getReader(); // 创建了一个文本解…...

图扑软件入选 2023 中国信通院“铸基计划”全景图

7 月 27 日&#xff0c;由中国信通院主办的“2023 数字生态发展大会”暨中国信通院“铸基计划”年中会议在北京召开。本次大会重磅发布了《高质量数字化转型产品及服务全景图&#xff08;2023 上半年度&#xff09;》。图扑软件凭借自研 HT for Web 数字孪生可视化产品成功入选…...

Go Fyne:一种现代的,跨平台的 GUI 开发工具集

Fyne 是一个用 Go 语言写的&#xff0c;提供丰富&#xff0c;现代化的用户界面&#xff08;UI&#xff09;的开发工具集。Fyne 提供了一种简单而直观的方式来构建应用程序&#xff0c;并且注意到它是跨平台的&#xff0c;可以在 Windows&#xff0c;macOS&#xff0c;Linux&…...

React Dva 操作models中的subscriptions讲述监听

接下来 我们来看一个models的属性 之前没有讲到的subscriptions 我们可以在自己有引入的任意一个models文件中这样写 subscriptions: {setup({ dispatch, history }) {console.log(dispatch);}, },这样 一进来 这个位置就会触发 这里 我们可以写多个 subscriptions: {setup…...

标准化归一化 batch norm, layer norm, group norm, instance norm

Layer Normalization - EXPLAINED (in Transformer Neural Networks) Layer Normalization - EXPLAINED (in Transformer Neural Networks) 0~4min:什么是multi-head attention 5~7min:layer norm图示 7~9min:公式举例layer norm 9:54-end:layer norm的代码示例 group n…...

Zabbix监控系统详解及配置

前言 作为一个运维&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果&#xff0c;和网站的健康状态。利用一个优秀的监控软件&#xff0c;我们可以&#xff1a; 通过一个友好的界面进行浏览整个网站所有的服务…...

【100天精通python】Day29:文件与IO操作_XML文件处理

目录 专栏导读 一、XML文件概述 1. 标签和元素 2. 嵌套结构 3. 属性 4. 命名空间 5. CDATA节 6. 注释 7. 验证与验证语言 8. 扩展性 二、XML文件处理常见操作 1. 解析XML文件 2. 创建和编辑XML文件 3. 修改XML文件 4. 查询XML元素 5 遍历XML元素 6. 删除XML元…...

人工智能的未来:探索下一代生成模型

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 生成式 AI 目前能够做什么&#xff0c;以及探索下一波生成式 AI 模型需要克服的当前挑战&#xff1f; 如果你跟上科技世界的步伐&#xff0c;你就会知道生成式人工智能是最热门的话题。我们听到了很多关于…...

C++ 运算符重载为非成员函数

运算符也可与重载为非成员函数。这时运算所需要的操作数都需要通过函数的形参表来传递&#xff0c;在形参表中形参从左到右的顺序就是运算符操作数的顺序。如果需要访问运算符参数对象的私有成员&#xff0c;可以将该函数声明为友元函数。 【提示】不用机械地将重载运算符的非…...

[国产MCU]-BL602开发实例-定时器

定时器 文章目录 定时器1、BL602定时器介绍2、定时器驱动API介绍3、定时器使用实例3.1 单次计时3.2 持续计时通用定时器,用于定时,当时间到达我们所设置的定时时间会产生定时中断,可以用来完成定时任务。本文将详细介绍如何使用BL602的定时器功能。 1、BL602定时器介绍 BL6…...

re学习(29)攻防世界-CatFly(复原反汇编)

因为这是一个.dll文件&#xff0c;在Linux上运行一下&#xff1a; 找到主要函数&#xff1a;&#xff08;以及由上面三部分对应的代码部分&#xff09; __int64 __fastcall main(int a1, char **a2, char **a3) {size_t v3; // rbx__int16 v5[4]; // [rsp10h] [rbp-4B0h] B…...

Android WIFI-概率性不能自连

1.连上wifi时同步保存wifi密码,避免连上wifi后马上断电重启由于密码没保存导致不能自动重连wifi packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java @@ -66,6 +66,7 @@ import com.android.server.wifi.WifiNative.SupplicantDeathEve…...

用Python批量复制文件,方法有9种,方便快捷

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 当我们复制一个文件时用复制粘贴就可以了&#xff0c;如果是多个文件呢&#xff1f; 就会很麻烦了&#xff01; 今天给大家介绍一下用Python批量复制文件&#xff0c;方法有九种&#xff01;希望对你有帮助 1. Shutil Copy…...

《凤凰架构》第一章——服务架构演进史

前言 刚开始决定弄懂文中所提到的所有东西&#xff0c;就像我写ByteByteGo呢几篇文章一样&#xff0c;把每一句话都弄懂。但是对于《凤凰架构》来说&#xff0c;这有点太费时间了&#xff0c;并且没有必要&#xff0c;有些东西可能永远都不会用到&#xff0c;但文章为了全面的…...

【iPhone】手机还有容量,拍视频却提示 iPhone 储存空间已满

文章目录 前言解决方案 结语 前言 今天在用 iPhone 录像的时候突然提醒我 iPhone储存空间已满 你没有足够的储存空间来录制视频” 可我明明还有 20G 的容量 我非常疑惑&#xff0c;因为我之前还剩1个G都能录像&#xff0c;现在20G反而不行了&#xff0c;于是重启了手机&#…...

pycharm中opencv库导入 cv2. 无函数提示跳出解决方法

pycharm中opencv库导入 cv2. 无函数提示跳出解决方法 1、找到当前解释器安装目录 例如&#xff1a; 2、进入D:\Python37\Lib\site-packages\cv2文件&#xff0c;进入cv2文件夹&#xff1a; 找到cv2.pyd, 把cv2.pyd复制一份&#xff0c;放到上层文件夹下&#xff0c;即site-p…...

week3

题解: 前序遍历性质&#xff1a; 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序。 中序遍历性质&#xff1a; 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序。 通过以上三步&#xff0c;可确定 三个节点 &#xff1a;1.树的根节点、2.左子树根节点、3.右子树根节点。 之后进行…...

LeetCode28.找出字符串中第一个匹配项的下标

28.找出字符串中第一个匹配项的下标 目录 28.找出字符串中第一个匹配项的下标题目描述解法一&#xff1a;朴素的模式匹配解法二&#xff1a;KMP算法KMP解决的问题类型最长公共前后缀KMP算法过程next数组的构建代码实现 题目描述 给你两个字符串haystack和needle&#xff0c;请…...

爬虫009_字符串高级_替换_去空格_分割_取长度_统计字符_间隔插入---python工作笔记028

然后再来看字符串的高级操作 取长度 查找字符串下标位置 判断是否以某个字符,开头结尾 计算字符出现次数 替换...

Windows 安装Tensorflow2.1、Pycharm开发环境

文章目录 1、安装anaconda2、安装Tensoflow2.1、创建虚拟环境2.2、安装Tensorflow依赖2.3、验证Tensorflow是否成功 3、配置pycharm环境4、错误记录 1、安装anaconda https://www.anaconda.com/download 打开命令行工具&#xff0c;出现base就表示安装成功了&#xff0c;表示当…...

【javaScript】数组的常用方法(自用记忆版)

目录 一、操作方法 增 push() unshift() splice() concat() 删 pop() shift() splice() slice() 改 splice() 查 indexOf() includes() find() 二、排序方法 reverse() sort() 三、转换方法 join() ​​​​​​四、迭代方法 some() every() forEach…...

全新二开美化版UI好看的社区源码下载/反编译版

2023全新二开美化版UI精美的社区源码下载/反编译版 之前我分享过Rule原版&#xff0c;相信大家已经有很多人搭建好了。这次我要分享的是RuleAPP的二开美化版&#xff08;请尊重每个作者的版权&#xff09;&#xff0c;这个版本没有加密&#xff0c;可以进行反编译&#xff0c;…...

Docker 发布一个springboot项目

文章目录 1、新建SpringBootDemo项目并打包2、使用Dockerfile打包&#xff08;基础用法&#xff09;进一步maven源码打包法 3、更进一步&#xff08;maven插件打包&#xff09;docker-maven-pluginspring-boot-maven-plugin前提条件本地环境配置项目环境配置maven插件打包运行校…...

办公信息系统安全基本技术要求

范围 本标准规定了办公信息系统的安全基本技术要求。 本标准适用于指导党政部门的办公信息系统建设&#xff0c;包括在系统设计、产品采购、系统集成等方面应遵循的基本原则&#xff0c;以及应满足的基本技术要求。涉密办公信息系统的建设管理应依据相关国家保密法规和标准要…...

有效管理IT问题的5个原则

问题管理就是发现未知的、隐藏的问题&#xff0c;这是根本原因&#xff0c; 这是您 IT 帮助台无穷无尽的工单来源。当您实施有效的 问题管理&#xff0c;您的 IT 团队可以超越消防模式&#xff0c;专注于战略 IT 目标。以下是可以帮助您实现一流问题管理的五个原则&#xff1a;…...

【MongoDB】解决ProxmoxVE下CentOS7虚拟机安装MongoDB6后启动失败的问题

目录 安装步骤: 2.1 配置yum源 2.2 安装MongoDB 2.3 启 动MongoDB ProxmoxVE上新装的CentOS7.4虚拟机,安装MongoDB6。 安装步骤: 2.1 配置yum源 # 创建mongodb yum源(https://www.mongodb.co...

MySQL 事务原理:事务概述、隔离级别、MVCC

文章目录 一、事务1.1 事务概述1.2 事务控制语句1.3 ACID特性 二、隔离级别2.1 隔离级别的分类2.1.1 读未提交&#xff08;RU&#xff09;2.1.2 读已提交&#xff08;RC&#xff09;2.1.3 可重复读&#xff08;RR&#xff09;2.1.4 串行化 2.2 命令2.3 并发读异常2.3.1 脏读2.3…...