Quartz深度实战

news2024/7/4 4:20:15

概述

Java语言中最正统的任务调度框架,几乎是首选。后来和Spring Schedule平分秋色;再后来会被一些轻量级的分布式任务调度平台,如XXL-Job取代。另外近几年Quartz的维护和发布几乎停滞,但这并不意味着Quartz被淘汰,还有学习和使用的价值。

入门

SB版本是2.0.0+,则spring-boot-starter-quartz中已经包含quart的依赖,可直接使用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

SB 1.5.9及以下版本,需添加依赖:

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
</dependency>

配置类QuartzConfig,一个简单的SimpleScheduleBuilder,每隔10s调度执行一次:

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail teatQuartzDetail() {
        return JobBuilder.newJob(TestQuartz.class).withIdentity("testQuartz").storeDurably().build();
    }

    @Bean
    public Trigger testQuartzTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10).repeatForever();
        return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())
                .withIdentity("testQuartz").withSchedule(scheduleBuilder).build();
    }
}

创建demo类继承QuartzJobBean:

@Slf4j
public class TestQuartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("quartz task " + new Date());
    }
}

控制台打印输出:

2022-11-24 22:02:06.517 [INFO][quartzScheduler_Worker-3]:com.johnny.demo.TestQuartz [executeInternal:16] quartz task Thu Nov 24 22:02:06 CST 2022
2022-11-24 22:02:16.518 [INFO][quartzScheduler_Worker-4]:com.johnny.demo.TestQuartz [executeInternal:16] quartz task Thu Nov 24 22:02:16 CST 2022

实战

实际应用中,一般都是使用CronTrigger:

@Slf4j
@Service
public class JobService implements InitializingBean {
    @Resource
    private SchedulerFactoryBean schedulerFactoryBean;

	private void configScheduler() {
		Scheduler scheduler = schedulerFactoryBean.getScheduler();
        try {
            scheduler.clear();
        } catch (SchedulerException e) {
            log.error("clear scheduler failed: ", e);
        }
		try {
			DashboardDataset dataset = datasetMapper.getDataset(id);
			// 数据集定时任务的cron表达式不为空
			if (StringUtils.isNotEmpty(dataset.getCronExp())) {
				JobDetail jobDetail = JobBuilder.newJob(DatasetJobExecutor.class).withIdentity("dataset_" + dataset.getId().toString()).build();
				CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("dataset_" + dataset.getId().toString()).withSchedule(CronScheduleBuilder.cronSchedule(dataset.getCronExp())).build();
				jobDetail.getJobDataMap().put("dataset", dataset);
				scheduler.scheduleJob(jobDetail, trigger);
			}
		} catch (Exception e) {
			logger.error("start dataset job error:{}", e);
		}
	}

    @Override
    public void afterPropertiesSet() throws Exception {
        configScheduler();
    }
}
public class DatasetJobExecutor implements Job {
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		DatasetDao datasetDao = ((ApplicationContext) context.getScheduler().getContext().get("applicationContext")).getBean(DatasetDao.class);
		// 业务逻辑
	}
}

配置

配置文件quartz.properties

org.quartz.scheduler.instanceName=quartzScheduler
org.quartz.scheduler.instanceId=AUTO
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=20000
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 设置线程池
org.quartz.threadPool.threadCount=200
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

原理

Quartz设计的核心类包括Scheduler,Job及Trigger。Job负责定义需要执行的任务,Trigger负责设置调度策略,Scheduler将二者组装在一起,并触发任务开始执行。
在这里插入图片描述

Job

Job接口的源码:

public interface Job {
	void execute(JobExecutionContext var1) throws JobExecutionException;
}

JobExecutionContext类提供调度应用的一些信息。使用者只需创建一个 Job 的实现类,实现其中的execute方法。

Trigger

用于设置调度策略。Quartz设计多种类型的Trigger,但最常用只有2种:

  1. SimpleTrigger
    适用于在某一特定的时间执行一次,或在某一特定的时间以某一特定时间间隔执行多次。参数包括 start-time,end-time,repeat count,repeat interval。
    • Repeat count:取值为>=0的整数,或常量SimpleTrigger.REPEAT_INDEFINITELY
    • Repeat interval:取值为>=0的长整型。当 Repeat interval 取值为零且Repeat count 取值大于零时,将会触发任务的并发执行
    • Start-time 与 end-time 取值为java.util.Date。同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY
  2. CronTrigger
    用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,适用于基于日历的调度安排,需要指定 start-time 和 end-time,Cron。Cron由七个字段组成:Seconds、Minutes、Hours、Day-of-Month、Month、Day-of-Week、Year (Optional field)。

Job 与 Trigger 的松耦合设计,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。

Scheduler

代表Quartz的一个独立运行容器, Trigger和JobDetail可注册到Scheduler中, 两者在Scheduler中拥有各自的组及名称, 组及名称是Scheduler查找定位容器中某一对象的依据, Trigger的组及名称必须唯一, JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中, 这样当Trigger触发时, 对应的Job就被执行。一个Job可以对应多个Trigger, 但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内部信息。SchedulerContext内部通过一个Map以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供多个put()和getXxx()的方法。可通过Scheduler.getContext()获取对应的SchedulerContext实例。

JobDetail

源码如下:

public interface JobDetail extends Serializable, Cloneable {
    public JobKey getKey();
    public String getDescription();
    /**
     * Get the instance of Job that will be executed.
     */
    public Class<? extends Job> getJobClass();
    /**
     * Get the JobDataMap that is associated with the Job.
     */
    public JobDataMap getJobDataMap();
    /**
     * Whether or not the Job should remain stored after it is orphaned (no @Triggers point to it).
     * If not explicitly set, the default value is false.
     * @return true if the Job should remain persisted after being orphaned.
     */
    public boolean isDurable();
    /**
     * @return whether the associated Job class carries the @PersistJobDataAfterExecution annotation.
     */
    public boolean isPersistJobDataAfterExecution();
    /**
     * @return whether the associated Job class carries the @DisallowConcurrentExecution annotation.
     */
    public boolean isConcurrentExectionDisallowed();
    /**
     * Instructs the Scheduler whether or not the Job should be re-executed if a 'recovery' or 'fail-over' situation is encountered.
     * If not explicitly set, the default value is false.
     */
    public boolean requestsRecovery();
    public Object clone();
    /**
     * Get a JobBuilder that is configured to produce a JobDetail identical to this one.
     */
    public JobBuilder getJobBuilder();
}

JobDetail负责封装Job及Job的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例。注意到,不是直接接受一个Job的实例,相反它接收一个Job实现类(即JobDetail),运行时通过newInstance()的反射机制实例化Job,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap可存储任意数量的 Key,Value 对。

任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。

为什么设计成JobDetail + Job,不直接使用Job?

JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中。因为任务有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以 规避并发访问 的问题。

Listener

Quartz提供Listener功能:JobListener,TriggerListener及 SchedulerListener。用于当系统发生故障,通知到配置的告警人。当任务被执行时系统发生故障,Listener监听到错误,立即发送邮件给管理员。

Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等,可注册相应的监听器处理感兴趣的事件。

Calendar

org.quartz.Calendarjava.util.Calendar不同, 是一些日历特定时间点的集合。 一个Trigger可以和多个Calendar关联, 以便排除或包含某些时间点。比如,安排每周一早上10:00执行任务,但碰到法定节假日,任务不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义;

ThreadPool

Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

有无状态

Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。StatefulJob接口在2.2.3版本已经被标记为@deprecated。

如果Quartz使用数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext.getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。

注解

@DisallowConcurrentExecution
如果任务不能支持并发执行(比如任务还没执行完, 下一轮就trigger时间到达, 如果没做同步处理可能造成严重的数据问题), 则在任务类加上注解:@DisallowConcurrentExecution,等待任务执行完毕以后再去执行。

@PersistJobDataAfterExecution

An annotation that marks a Job class as one that makes updates to its JobDataMap during execution, and wishes the scheduler to re-store the JobDataMap when execution completes.
Jobs that are marked with this annotation should also seriously consider using the @DisallowConcurrentExecution annotation, to avoid data storage race conditions with concurrently executing job instances.

执行后保留作业数据

插件

即Plug-Ins,Quartz提供一个SPI接口org.quartz.spi.SchedulerPlugin来实现插件(plugging-in)功能。装配给Quartz的Plugins能提供不同的有用功能,参考org.quartz.plugins包下面已经提供的插件:

  1. LoggingJobHistoryPlugin & LoggingTriggerHistoryPlugin:调度器启动时自动调度jobs,记录job和triggers事件的历史;
  2. ShutdownHookPlugin:当JVM退出时确保调度器关闭;
  3. XMLSchedulingDataProcessorPlugin:可通过配置属性文件来使用自己实现或Quartz自带的插件。

misfire

不触发指令

总结

Scheduler的内部组件结构,SchedulerContext提供Scheduler全局可见的上下文信息,每一个任务都对应一个JobDataMap,虚线表达的JobDataMap表示对应有状态的任务:
在这里插入图片描述

集群

生产环境中使用Quartz时,服务不可能只部署在一个节点,容易出现单点故障,至少都有2个节点。这种分布式集群情况,Quart自然也是支持的,集群架构图:
在这里插入图片描述
根据这个图,不难得出结论:Quartz是通过数据库锁(悲观锁)来保证多个节点的应用只进行一次调度,即某一时刻的调度任务只由其中一台服务器执行。

集群配置项:org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX,从源码可看到JobStoreCMT extends JobStoreSupport

QuartzSchedulerThread职责是触发任务, 是一个不间断运行的Quartz主线程,在QuartzSchedulerThread的run()方法里面调用的acquireNextTriggers、 triggersFired、 releaseAcquiredTrigger方法都进行加锁处理。

JobStoreSupport.acquireNextTriggers为例:

public List<OperableTrigger> acquireNextTriggers(final long noLaterThan, final int maxCount, final long timeWindow) throws JobPersistenceException {
    String lockName;
    if(isAcquireTriggersWithinLock() || maxCount > 1) { 
        lockName = LOCK_TRIGGER_ACCESS;
    } else {
        lockName = null;
    }
}

LOCK_TRIGGER_ACCESS是个常量:
protected static final String LOCK_TRIGGER_ACCESS = "TRIGGER_ACCESS";
这个常量传入加锁的核心方法executeInNonManagedTXLock: 处理逻辑前获取锁, 处理完成后在finally里面释放锁(一种典型的同步处理方法)

protected <T> T executeInNonManagedTXLock(String lockName, TransactionCallback<T> txCallback, final TransactionValidator<T> txValidator) throws JobPersistenceException {
    boolean transOwner = false;
    Connection conn = null;
    try {
        if (lockName != null) {
            // If we aren't using db locks, then delay getting DB connection until after acquiring the lock since it isn't needed.  
            if (getLockHandler().requiresConnection()) {
                conn = getNonManagedTXConnection();
            }
            // 获取锁  
            transOwner = getLockHandler().obtainLock(conn, lockName);
        }
        if (conn == null) {
            conn = getNonManagedTXConnection();
        }
        final T result = txCallback.execute(conn);
        try {
            commitConnection(conn);
        } catch (JobPersistenceException e) {
            rollbackConnection(conn);
            if (txValidator == null || !retryExecuteInNonManagedTXLock(lockName, new TransactionCallback<Boolean>() {
                @Override
                public Boolean execute(Connection conn) throws JobPersistenceException {
                    return txValidator.validate(conn, result);
                }
            })) {
                throw e;
            }
        }
        Long sigTime = clearAndGetSignalSchedulingChangeOnTxCompletion();
        if (sigTime != null && sigTime >= 0) {
            signalSchedulingChangeImmediately(sigTime);
        }
        return result;
    } catch (JobPersistenceException e) {
        rollbackConnection(conn);
        throw e;
    } catch (RuntimeException e) {
        rollbackConnection(conn);
        throw new JobPersistenceException("Unexpected runtime exception: " + e.getMessage(), e);
    } finally {
        try {
            // 释放锁  
            releaseLock(lockName, transOwner);
        } finally {
            cleanupConnection(conn);
        }
    }
}

getLockHandler那么可以思考下这个LockHandler怎么来的?最后发现在JobStoreSupport的initialize方法赋值:

public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
    // If the user hasn't specified an explicit lock handler, then choose one based on CMT/Clustered/UseDBLocks.  
    if (getLockHandler() == null) {
        // If the user hasn't specified an explicit lock handler, then we *must* use DB locks with clustering  
        if (isClustered()) {
            setUseDBLocks(true);
        }
        if (getUseDBLocks()) {
            // 在初始化方法里面赋值
            setLockHandler(new StdRowLockSemaphore(getTablePrefix(), getInstanceName(), getSelectWithLockSQL()));
        } else {
            getLog().info("Using thread monitor-based data access locking (synchronization).");
            setLockHandler(new SimpleSemaphore());
        }
    }
}

可以在StdRowLockSemaphore里面看到:

public static final String SELECT_FOR_LOCK = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_LOCKS + " WHERE " + COL_SCHEDULER_NAME + " = " + SCHED_NAME_SUBST + " AND " + COL_LOCK_NAME + " = ? FOR UPDATE";
public static final String INSERT_LOCK = "INSERT INTO " + TABLE_PREFIX_SUBST + TABLE_LOCKS + "(" + COL_SCHEDULER_NAME + ", " + COL_LOCK_NAME + ") VALUES (" + SCHED_NAME_SUBST + ", ?)";

可看出采用悲观锁的方式对triggers表进行加行锁的细粒度锁, 以保证任务同步的正确性。

当线程使用上述的SQL对表中的数据执行操作时,数据库对该行进行行加锁; 于此同时, 另一个线程对该行数据执行操作前需要获取锁, 而此时已被占用, 那么这个线程就只能等待, 直到该行锁被释放。Quartz的锁存放在:

CREATE TABLE `scheduler_locks` (  
  `SCHED_NAME` varchar(120) NOT NULL COMMENT '调度名',  
  `LOCK_NAME` varchar(40) NOT NULL COMMENT '锁名',  
  PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8

锁名和上述常量一一对应:

SCHED_NAMELOCK_NAME
schedulerSTATE_ACCESS
schedulerTRIGGER_ACCESS

JobStoreTX & JobStoreCMT

查看JobStoreCMT源码:

JobStoreCMT is meant to be used in an application-server environment that provides container-managed-transactions. No commit / rollback will be handled by this class.
If you need commit / rollback,use JobStoreTX instead.

查看JobStoreTX源码:

JobStoreTX is meant to be used in a standalone environment. Both commit and rollback will be handled by this class.
If you need a JobStore class to use within an application-server environment, use JobStoreCMT instead.

翻译后的大致意思:
JobStoreCMT不管理事务,将事务托管给配置文件中指定的全局事务管理程序去管理事务并参与到全局事务(JTA)。完成数据库操作时,不提交(commit),由全局的事务管理程序来对全局事务进行统一的提交或回滚。CMT还可以配置一个不参与全局事务的数据库连接,用于配置如quartz的持久化数据库表,在持久化Job和Tirgger后自动提交。

如果你不需要绑定其他事务处理,可使用quartz自带的事务,即JobStoreTX方式,这也是最常见的选择。

参考

  • 2.3.2 API document

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

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

相关文章

【干货】微信私域运营打法和案例拆解(附78页pdf下载链接)

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年10月份热门报告盘点知识图谱在美团推荐场景中的应用实践.pdf清华大学256页PPT元宇宙研究报告.pdf&#xff08;附下载链接&#xff09;机器学习在B站推荐系统中的应用实践…

【吴恩达机器学习笔记】七、神经网络

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

数据库设计三范式

数据库设计三范式 范式是数据库设计时遵循的一种规范&#xff0c;不同的规范需要遵循不同的范式&#xff0c;只有充分遵循了数据库设计的范式&#xff0c;才能设计开发出冗余较小、高效、结构合理的数据库。 通常&#xff0c;我们在设计数据库的时候会要求遵循三范式。 第一…

《强化学习周刊》第69期:ICLR2023强化学习论文推荐、MIT实现自动调整内在奖励的强化学习...

No.69智源社区强化学习组强化学习研究观点资源活动周刊订阅告诉大家一个好消息&#xff0c;《强化学习周刊》已经开启“订阅功能”&#xff0c;以后我们会向您自动推送最新版的《强化学习周刊》。订阅方法&#xff1a;方式1&#xff1a;扫描下面二维码&#xff0c;进入《强化学…

使用Python PySNMP模块获取设备指标

一、PySNMP模块介绍&#xff1a; PySNMP 是一个跨平台的纯Python SNMP 引擎实现。它具有功能齐全的 SNMP 引擎&#xff0c;能够充当代理/管理器/代理角色&#xff0c;通过 IPv4/IPv6 和其他网络传输传输 SNMP v1/v2c/v3 协议版本。目前&#xff0c;使用较多的是SNMP v3和v2c版…

Robust Document Image Dewarping Method Using Text-Lines and Line Segments论文学习笔记

1 摘要 传统的基于文本行的文档去扭曲方法在处理复杂布局和/或非常少的文本行时会出现问题。当图像中没有对齐的文本行时&#xff0c;这通常意味着照片、图形和/或表格占据了输入的大部分。因此&#xff0c;对于健壮的文档去扭曲变形&#xff0c;我们建议除了对齐的文本行之外…

Python解题 - CSDN周赛第11期 - 圆桌请客(脑筋急转弯)

本来想着没有all pass就不写题解了&#xff0c;但在赛后对最后一题纠结了好久&#xff0c;然后发现是个类似脑筋急转弯的题&#xff0c;自己与正确答案只差一层纸&#xff0c;实在有点不吐不快。另外本期考了经典的背包问题的模板题&#xff0c;也值得记录下来&#xff0c;加深…

全志科技A40i国产开发板——性能参数综合测试

本次测试板卡是创龙科技旗下,一款基于全志科技A40i开发板,其接口资源丰富,可引出双路网口、双路CAN、双路USB、双路RS485等通信接口,板载Bluetooth、WIFI、4G(选配)模块,同时引出MIPI LCD、LVDS LCD、TFT LCD、HDMI OUT、CVBS OUT、CAMERA、LINE IN、H/P OUT等音视频多媒…

宿主机与开发板网络共享

宿主机网络共享 一、关键步骤 11. 网络共享简介 目标&#xff1a;宿主机可以用ssh连接开发板&#xff0c;开发板可以上网。 步骤&#xff1a;宿主机与目标机用网线直连&#xff0c;宿主机采用IP共享的方式连接开发板&#xff1b; 配置项IP开发板IP192.168.0.232宿主机以太网I…

Java 序列化原理

我的网站 | 我的博客 | 序列化解析工具 概念 Java为我们提供了一种默认的对象序列化机制&#xff0c;通过这种机制可以将一个实例对象写入到IO流中&#xff0c;当然这种IO流可以是文件流、网络流或者其他什么流。 代码的写法 ObjectOutputStream 对象输出流&#xff0c;用…

2022新一代设备维修管理系统助力企业降本增效

设备的维修是指企业或者设备密集型单位为了保持、恢复并提升设备使用寿命而定期对设备进行状态的维护&#xff0c;备件的更换&#xff0c;发生故障后的维修和恢复&#xff0c;从而让设备保证良好的运营状态&#xff0c;提升设备的可利用性并保证产能和设备安全。 大型企业在设…

C++11标准模板(STL)- 算法(std::merge)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 归并两个已排序的范围 st…

关于Mysql使用left join写查询语句执行很慢的问题解决

目录 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;正文 1. 表结构/索引展示 &#xff08;1&#xff09;表结构 &#xff08;2&#xff09;各表索引情况 2. 存在性能问题的SQL语句 3. 解决思路 &#xff08;1&#xff09;执行计划思路调优 &#xff08;…

数字图像处理(入门篇)二 颜色空间

在对图像进行处理时&#xff0c;前提图像必须是以数据的形式来描述的&#xff0c;而颜色空间就是用数据来表征图像颜色的一种方法。颜色信息由三个独立的分量来综合表示&#xff0c;这三个独立的分量构成了一个三维的坐标空间&#xff0c;每种颜色信息都在该空间中被唯一地表示…

Java-泛型实验

1.定义一个学生类Student&#xff0c;具有年龄age和姓名name两个属性&#xff0c;并通过实现Comparable接口提供比较规则&#xff08;返回两个学生的年龄差&#xff09;&#xff0c; 定义测试类Test&#xff0c;在测试类中定义测试方法Comparable getMax(Comparable c1, Compar…

基于springboot农机电招平台设计与实现的源码+文档

摘要 随着农机电招行业的不断发展&#xff0c;农机电招在现实生活中的使用和普及&#xff0c;农机电招行业成为近年内出现的一个新行业&#xff0c;并且能够成为大群众广为认可和接受的行为和选择。设计农机电招平台的目的就是借助计算机让复杂的销售操作变简单&#xff0c;变高…

Kubernetes资源调度之节点亲和

Kubernetes资源调度之节点亲和 Pod节点选择器 nodeSelector指定的标签选择器过滤符合条件的节点作为可用目标节点&#xff0c;最终选择则基于打分机制完成。因此&#xff0c;后者也称为节点选择器。用户事先为特定部分的Node资源对象设定好标签&#xff0c;而后即可配置Pod通过…

YOLO X 改进详解

YOLO X 主要改进&#xff1a; Anchor-Free: FCOSDecoupled detection headAdvanced label assigning strategy Network structure improvement Decoupled detection head 对比于YOLO V5, YOLO X 在detection head上有了改进。YOLO V5中&#xff0c;检测头是通过卷积同时预…

ROS2--概述

ROS2概述1 ROS2对比ROS12 ROS2 通信3 核心概念4 ros2 安装5 话题、服务、动作6 参数参考1 ROS2对比ROS1 多机器人系统&#xff1a;未来机器人一定不会是独立的个体&#xff0c;机器人和机器人之间也需要通信和协作&#xff0c;ROS2为多机器人系统的应用提供了标准方法和通信机…