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

Quartz中Misfire机制源码级解析

文章目录

        • 前文
        • 案例展示
        • Misfire机制
          • 1. 启动过程补偿
          • 2. 定时任务补偿
          • 3. 查询待触发列表时间区间补偿

前文

Misfire是啥意义的?使用翻译软件结果是"失火"。一个定时软件,还来失火?其实在Java里面,fire的含义更应该是触发,比如在Netty中就常见这个词
在这里插入图片描述
同样在Quartz中,这个fire也是到处可见。比如QRTZ_TRIGGERS表中的NEXT_FIRE_TIME(下一次触发时间)、PREV_FIRE_TIME(上一次触发时间)。所以这里Misfire的意思是错过触发。Quartz作为根据时间来定时触发的框架,不可避免的一个问题就是针对错过触发的任务的补偿机制。很多人难以理解Quartz怎么会错过任务触发呢?这是什么Bug吗?哪些场景下会导致任务错过触发呢?首先,如果程序停机了,此时由于Quartz调度线程都不在了,当然不会继续执行任务,当程序再次重启时,此时在停机时间内该调度的任务可能认为是错过触发的,需要进行补偿。其次,即使程序正常运行,由于工作线程数量限制和某些任务执行时间过长,也会导致任务错过触发,毕竟干活的都是工作线程,工作线程没有空闲,即使任务调度时间到了,也轮不到触发。这么分析下来,这些算不上是Quartz的BUG。通常来说,工作线程的数量org.quartz.threadPool.threadCount必须根据业务情况来合理设置,另外,通过Quartz触发的任务应该尽量时间短或可控。但补偿机制还是必不可少的,毕竟谁也不能保证一切可控。

案例展示

下面通过一段代码来演示一些导致错过触发任务的场景,这里创建一个耗时3分钟的任务来模拟耗时较长的任务

package org.quartz.myexample;import org.quartz.*;import java.util.Date;public class LongTimeJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {System.err.println("---" + context.getJobDetail().getKey() + " executing.[" + new Date() + "]");try {// 3分钟Thread.sleep(60000 * 3);} catch (Exception ignore) {//}System.err.println("  -" + context.getJobDetail().getKey() + " complete.[" + new Date() + "]");}
}

然后设置只使用一个工作线程

// 限制只有一个工作线程 系统参数会覆盖配置文件中的参数
System.setProperty("org.quartz.threadPool.threadCount", "1");

同时要求任务启动时候为上一个整点时间,然后每10秒执行一次任务

Date startTime = DateBuilder.evenHourDateBefore(new Date());// Trigger the job to run now, and then repeat every 40 seconds
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("mytrigger", "group1").startAt(startTime).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();

src/main/resources/quartz.properties配置文件内容如下,这里使用MySQL数据库作为JobStore

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
### 此调度程序的名称
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#org.quartz.scheduler.rmi.export: false
#org.quartz.scheduler.rmi.proxy: false
#org.quartz.scheduler.wrapJobExecutionInUserTransaction: false#org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool## 线程池中有3个线程,这意味着最多可以同时运行3个job
org.quartz.threadPool.threadCount: 3
org.quartz.threadPool.threadPriority: 5
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true#org.quartz.jobStore.misfireThreshold: 60000
# quartz的所有数据,包括job和trigger的配置,都会存储在内存中
#org.quartz.jobStore.class: org.quartz.simpl.RAMJobStoreorg.quartz.scheduler.skipUpdateCheck=true
#org.terracotta.quartz.skipUpdateCheck=true
org.quartz.managementRESTService.enabled=falseorg.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.dataSource = myDS
org.quartz.dataSource.myDS.connectionProvider.class:com.alibaba.druid.support.quartz.DruidQuartzConnectionProvider
org.quartz.dataSource.myDS.driverClassName = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.url = jdbc:mysql://localhost:3306/quartz?characterEncoding=utf-8
org.quartz.dataSource.myDS.username = root
org.quartz.dataSource.myDS.password = root
org.quartz.dataSource.myDS.maxActive = 5org.quartz.scheduler.instanceId=AUTO

logback日志文件src/main/resources/logback.xml如下所示,将Quartz的日志级别调低到trace

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" debug="true"><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><!-- encoders are assigned the typech.qos.logback.classic.encoder.PatternLayoutEncoder by default --><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern></encoder></appender><logger name="org.quartz" level="trace"/><logger name="org.quartz.impl.jdbcjobstore.SimpleSemaphore" level="warn"/><root level="debug"><appender-ref ref="STDOUT"/></root></configuration>

完整测试类为QuartzTest4Misfire,启动时需要设置启动参数-Dorg.quartz.properties=quartz.properties文件的绝对路径

package org.quartz.myexample;import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;import java.util.Date;/*** -Dorg.quartz.properties=D:\Tools\activtiDemo\src\main\resources\quartz.properties*/
public class QuartzTest4Misfire {public static void main(String[] args) {try {// 限制只有一个工作线程System.setProperty("org.quartz.threadPool.threadCount", "1");// Grab the scheduler instance from the FactoryScheduler scheduler = StdSchedulerFactory.getDefaultScheduler();scheduler.getContext().put("skey", "svalue");Date startTime = DateBuilder.evenHourDateBefore(new Date());System.err.println("开始触发时间" + startTime);// Trigger the job to run now, and then repeat every 40 secondsTrigger trigger = TriggerBuilder.newTrigger().withIdentity("mytrigger", "group1").startAt(startTime).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();// define the job and tie it to our HelloJob classJobDetail job = JobBuilder.newJob(LongTimeJob.class).withIdentity("myjob", "group1").build();scheduler.deleteJob(job.getKey());// Tell quartz to schedule the job using our triggerscheduler.scheduleJob(job, trigger);// and start itscheduler.start();Thread.sleep(Integer.MAX_VALUE);scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}

首先在Quartz启动过程中可以看到以下内容。Handling 1 trigger(s) that missed their scheduled fire-time,很明显,这里是处理了一个错过触发的触发任务。
在这里插入图片描述
在项目运行两分钟之后,又可以看到如下内容
在这里插入图片描述
日志简化如下

MisfireHandler: scanning for misfires...
Handling 1 trigger(s) that missed their scheduled fire-time.

这里有一个名称为QuartzScheduler_DefaultQuartzScheduler-NON_CLUSTERED_MisfireHandler的线程在每隔1分钟执行一次scanning for misfires操作,本次处理了一个错误触发的任务。

Misfire机制

从上面案例中,可以发现在Quartz启动过程中、启动后定时进行Misfire补偿,其实除了这两个地方之外,在任务调度时也会考虑补偿。总共有如下三种补偿机制。

1. 启动过程补偿

源码对应为QuartzScheduler#start-> JobStoreSupport#schedulerStartedJob(非集群模式)-> JobStoreSupport#recoverJobs-> recoverMisfiredJobs(recovering为true)

2. 定时任务补偿

JobStoreSupport.MisfireHandler-> MisfireHandler#manage-> JobStoreSupport#doRecoverMisfires-> recoverMisfiredJobs(recovering为false)
该定时任务也是在QuartzScheduler#start过程中创建的。

3. 查询待触发列表时间区间补偿

QuartzSchedulerThread#run-> JobStoreSupport#acquireNextTrigger-> getDelegate().selectTriggerToAcquire

前两个最终都是调用org.quartz.impl.jdbcjobstore.JobStoreSupport#recoverMisfiredJobs方法,只是recovering值不同,后面会详述。查询待触发任务时会getMisfireTime作为noEarlierThan参数值,这样在当前时间前一段时间到未来一段时间的区间内的触发认为都有效,当前时间之前这段时间的触发任务其实就是一种补偿机制。而且在真实执行时,对于补偿策略配置为MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY的触发器,只要nextFireTime不超过noLaterThan最大值,都会认为满足触发条件。详细细节参考Quartz项目搭建与任务执行源码分析的任务调度一节即可。

这里详细介绍recoverMisfiredJobs中的补偿机制详情。其源码如下

protected RecoverMisfiredJobsResult recoverMisfiredJobs(Connection conn, boolean recovering)throws JobPersistenceException, SQLException {// If recovering, we want to handle all of the misfired// triggers right away.int maxMisfiresToHandleAtATime = (recovering) ? -1 : getMaxMisfiresToHandleAtATime();List<TriggerKey> misfiredTriggers = new LinkedList<TriggerKey>();long earliestNewTime = Long.MAX_VALUE;// We must still look for the MISFIRED state in case triggers were left // in this state when upgrading to this version that does not support it. boolean hasMoreMisfiredTriggers =getDelegate().hasMisfiredTriggersInState(conn, STATE_WAITING, getMisfireTime(), maxMisfiresToHandleAtATime, misfiredTriggers);if (hasMoreMisfiredTriggers) {getLog().info("Handling the first " + misfiredTriggers.size() +" triggers that missed their scheduled fire-time.  " +"More misfired triggers remain to be processed.");} else if (misfiredTriggers.size() > 0) { getLog().info("Handling " + misfiredTriggers.size() + " trigger(s) that missed their scheduled fire-time.");} else {getLog().debug("Found 0 triggers that missed their scheduled fire-time.");return RecoverMisfiredJobsResult.NO_OP; }for (TriggerKey triggerKey: misfiredTriggers) {OperableTrigger trig = retrieveTrigger(conn, triggerKey);if (trig == null) {continue;}doUpdateOfMisfiredTrigger(conn, trig, false, STATE_WAITING, recovering);if(trig.getNextFireTime() != null && trig.getNextFireTime().getTime() < earliestNewTime)earliestNewTime = trig.getNextFireTime().getTime();}return new RecoverMisfiredJobsResult(hasMoreMisfiredTriggers, misfiredTriggers.size(), earliestNewTime);
}

这里主要的逻辑就是通过DriverDelegate#hasMisfiredTriggersInState查询满足Misfire条件的Tiggers,并将结果存放在misfiredTriggers列表,然后遍历通过JobStoreSupport#doUpdateOfMisfiredTrigger修改内容。如果recovering为true,则单次查询条数为Long.MAX_VALUE,否则默认为20,这里主要是性能考虑。查询语句如下

SELECT COUNT(TRIGGER_NAME) 
FROM QRTZ_TRIGGERS 
WHERE SCHED_NAME = 'DefaultQuartzScheduler' 
AND NOT (MISFIRE_INSTR = -1) 
AND NEXT_FIRE_TIME < ? 
AND TRIGGER_STATE = 'WAITING'

其中NEXT_FIRE_TIME的值取决于misfireThreshold 的值。默认为当前时间减去一分钟,用户可以通过参数org.quartz.jobStore.misfireThreshold来修改,但是这个配置值如果小于0,其实是没有效果的,最终会按照当前时间计算。

private long misfireThreshold = 60000L; // one minuteprotected long getMisfireTime() {long misfireTime = System.currentTimeMillis();if (getMisfireThreshold() > 0) {misfireTime -= getMisfireThreshold();}return (misfireTime > 0) ? misfireTime : 0;
}

只要状态为WAITING,下一次触发时间为前一分钟之内而且Misfire补偿机制不为-1(MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)的QRTZ_TRIGGERS表数据,都是需要进行补偿的触发器。
查询出待补偿数据之后,就会执行doUpdateOfMisfiredTrigger方法进行补偿了。

private void doUpdateOfMisfiredTrigger(Connection conn, OperableTrigger trig, boolean forceState, String newStateIfNotComplete, boolean recovering) throws JobPersistenceException {Calendar cal = null;if (trig.getCalendarName() != null) {cal = retrieveCalendar(conn, trig.getCalendarName());}schedSignaler.notifyTriggerListenersMisfired(trig);trig.updateAfterMisfire(cal);if (trig.getNextFireTime() == null) {storeTrigger(conn, trig,null, true, STATE_COMPLETE, forceState, recovering);schedSignaler.notifySchedulerListenersFinalized(trig);} else {storeTrigger(conn, trig, null, true, newStateIfNotComplete,forceState, false);}
}

这里storeTrigger修改数据库数据无非就是修改触发器的状态和下一次触发时间这些值,如果没有下一次触发时间,则状态为COMPLETE,否则为WAITING。最关键的是下一次触发时间的计算,这里trig.updateAfterMisfire(cal)是关键。这里会根据用户设置的补偿策略来计算触发时间。什么是补偿策略呢?在前面已经看到的MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY就是补偿策略的一种。
在创建触发器的时候可以指定补偿策略,如果不指定则为MISFIRE_INSTRUCTION_SMART_POLICY

在这里插入图片描述
触发器的补偿策略可以通过方法org.quartz.Trigger#getMisfireInstruction来获取,在数据库当中对应表QRTZ_TRIGGERSMISFIRE_INSTR字段。

/*** Get the instruction the <code>Scheduler</code> should be given for* handling misfire situations for this <code>Trigger</code>- the* concrete <code>Trigger</code> type that you are using will have* defined a set of additional <code>MISFIRE_INSTRUCTION_XXX</code>* constants that may be set as this property's value.* * <p>* If not explicitly set, the default value is <code>MISFIRE_INSTRUCTION_SMART_POLICY</code>.* </p>* * @see #MISFIRE_INSTRUCTION_SMART_POLICY* @see SimpleTrigger* @see CronTrigger*/
public int getMisfireInstruction();

在源码当中我们会发现其实在Trigger接口中补偿策略只有两种,
MISFIRE_INSTRUCTION_SMART_POLICYMISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
在这里插入图片描述

前者会根据环境自动选择,后者则忽略补偿策略(也就是前面看到的-1),这种情况无论下一次触发时间有多早,都会在查询待触发列表时被查出来然后执行。
然后在CronTrigger当中,
在这里插入图片描述

我们又可以看到两种策略,MISFIRE_INSTRUCTION_FIRE_ONCE_NOWMISFIRE_INSTRUCTION_DO_NOTHING,在org.quartz.impl.triggers.CronTriggerImpl#updateAfterMisfire当中可以看到这两个值是如何影响下一次触发时间的。

@Override
public void updateAfterMisfire(org.quartz.Calendar cal) {int instr = getMisfireInstruction();if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY)return;if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) {instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW;}if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) {Date newFireTime = getFireTimeAfter(new Date());while (newFireTime != null && cal != null&& !cal.isTimeIncluded(newFireTime.getTime())) {newFireTime = getFireTimeAfter(newFireTime);}setNextFireTime(newFireTime);} else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) {setNextFireTime(new Date());}
}

可以看到,配置为SMART_POLICY或是FIRE_ONCE_NOW都是立即触发补偿,而DO_NOTHING则等到下一次触发(其实就是不进行任何补偿)。但是到了SimpleTrigger当中,这一切变得复杂起来了。
在这里插入图片描述
这里有5个策略,除了第一个立即触发之外,都会考虑一个_COUNT,这主要是对于普通触发器有一个repeatCount参数用于控制任务的触发次数,对于CronTrigger其实隐含这个值是无穷大,减不减一根本不影响结果。但是SimpleTrigger就不行,补偿机制必须考虑这个次数是否补偿。对于默认情况下,也会根据repeatCount自动调整策略。

if (instr == Trigger.MISFIRE_INSTRUCTION_SMART_POLICY) {if (getRepeatCount() == 0) {instr = MISFIRE_INSTRUCTION_FIRE_NOW;} else if (getRepeatCount() == REPEAT_INDEFINITELY) {instr = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;} else {// if (getRepeatCount() > 0)instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;}
} else if (instr == MISFIRE_INSTRUCTION_FIRE_NOW && getRepeatCount() != 0) {instr = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT;
}

默认情况下

repeatCount策略备注
0FIRE_NOW
无穷值RESCHEDULE_NEXT_WITH_REMAINING_COUNT
0RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

repeatCount不为空,但设置策略为FIRE_NOW也会调整为RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT。因为立即触发只是简简单单计算下一次触发时间。

  • RESCHEDULE_NEXT_WITH_EXISTING_COUNT不考虑补偿触发时间和触发次数。
else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) {Date newFireTime = getFireTimeAfter(new Date());while (newFireTime != null && cal != null&& !cal.isTimeIncluded(newFireTime.getTime())) {newFireTime = getFireTimeAfter(newFireTime);if(newFireTime == null)break;//avoid infinite loopjava.util.Calendar c = java.util.Calendar.getInstance();c.setTime(newFireTime);if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {newFireTime = null;}}setNextFireTime(newFireTime);
} 
  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT会计算错过触发的次数并补偿到timesTriggered字段,不会改变repeatCount
else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) {Date newFireTime = getFireTimeAfter(new Date());while (newFireTime != null && cal != null&& !cal.isTimeIncluded(newFireTime.getTime())) {newFireTime = getFireTimeAfter(newFireTime);if(newFireTime == null)break;//avoid infinite loopjava.util.Calendar c = java.util.Calendar.getInstance();c.setTime(newFireTime);if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) {newFireTime = null;}}if (newFireTime != null) {int timesMissed = computeNumTimesFiredBetween(nextFireTime,newFireTime);setTimesTriggered(getTimesTriggered() + timesMissed);}setNextFireTime(newFireTime);
} 
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT会重新计算repeatCount并将timesTriggered参数清零。并将当前时间作为触发时间进行补偿,但这里会考虑设置的结束时间,如果已经到了结束时间,则不再触发。
else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) {Date newFireTime = new Date();if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {setRepeatCount(getRepeatCount() - getTimesTriggered());setTimesTriggered(0);}if (getEndTime() != null && getEndTime().before(newFireTime)) {setNextFireTime(null); // We are past the end time} else {setStartTime(newFireTime);setNextFireTime(newFireTime);} 
} 
  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT会重新计算剩余的触发次数(包含错过触发的次数),更新repeatCount并将timesTriggered清零,将当前时间作为下一次触发时间。这里会考虑设置的结束时间,如果已经到了结束时间,则不再触发。
else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) {Date newFireTime = new Date();int timesMissed = computeNumTimesFiredBetween(nextFireTime,newFireTime);if (repeatCount != 0 && repeatCount != REPEAT_INDEFINITELY) {int remainingCount = getRepeatCount()- (getTimesTriggered() + timesMissed);if (remainingCount <= 0) { remainingCount = 0;}setRepeatCount(remainingCount);setTimesTriggered(0);}if (getEndTime() != null && getEndTime().before(newFireTime)) {setNextFireTime(null); // We are past the end time} else {setStartTime(newFireTime);setNextFireTime(newFireTime);} 
}
补偿策略枚举值补偿机制
MISFIRE_INSTRUCTION_FIRE_NOW1当前时间作为下一次触发时间
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT5重新计算下一次触发时间
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT4重新计算下一次触发时间,将错过触发次数补偿到timesTriggered字段
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT2重新计算repeatCount(原值-timesTriggered),timesTriggered清零,当前时间作为下一次触发时间,考虑是否已到结束时间
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT3重新计算repeatCount(原值-timesTriggered-错过触发次数),timesTriggered清零,当前时间作为下一次触发时间,考虑是否已到结束时间

总结:Quartz为了补偿Misfire,为用户提供可选用的各种补偿策略,并在初始化、查询触发任务以及创建了一个后台线程定时进行补偿。但是无论哪种补偿机制,终究都是补偿。正确了解后面的机制,合理设置工作线程大小,创建任务时考虑任务时长,合理设置触发时间,才是根本之道。

相关文章:

Quartz中Misfire机制源码级解析

文章目录 前文案例展示Misfire机制1. 启动过程补偿2. 定时任务补偿3. 查询待触发列表时间区间补偿 前文 Misfire是啥意义的&#xff1f;使用翻译软件结果是"失火"。一个定时软件&#xff0c;还来失火&#xff1f;其实在Java里面&#xff0c;fire的含义更应该是触发&…...

每日一题——重建二叉树

重建二叉树 题目描述 给定节点数为 n 的二叉树的前序遍历和中序遍历结果&#xff0c;请重建出该二叉树并返回它的头结点。 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}&#xff0c;则重建出如下图所示。 提示: 1.vin.length pre.length 2.pre 和…...

Python - json与字典dict

Python中的JSON和字典都是数据序列化的格式&#xff0c;它们都可以将数据转换为字符串以便于存储或传输。虽然它们有一些相似之处&#xff0c;但也有很多不同之处。 字典 字典是Python中的一种数据类型&#xff0c;它是一个键值对的集合。每个键对应一个值&#xff0c;可以通…...

性能测试必备监控技能linux篇

前言 如果性能测试的目标服务器是linux系统&#xff0c;在如何使用linux自带的命令来实现性能测试过程的监控分析呢&#xff1f; 对于日常性能测试来讲&#xff0c;在linux下或是类Unix系统&#xff0c;我们必须掌握以下常用的指标查看命令。 ps pstree top free vmstat …...

【如何训练一个中英翻译模型】LSTM机器翻译模型部署之ncnn(python)(五)

系列文章 【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训练…...

C++ 面向对象三大特征

文章目录 一、封装二、继承三、多态 一、封装 目的&#xff1a;隐藏实现细节&#xff1b;模块化 特性&#xff1a; 1&#xff09; 访问权限&#xff1a; public 所有 protected 子类 private 自己&#xff08;友元类也可以访问&#xff09; 2&#xff09;属性 3&#xff09;方…...

【Github】自动监测 SSL 证书过期的轻量级监控方案 - Domain Admin

在现代的企业网络中&#xff0c;网站安全和可靠性是至关重要的。一个不注意的SSL证书过期可能导致网站出现问题&#xff0c;给公司业务带来严重的影响。针对这个问题&#xff0c;手动检测每个域名和机器的证书状态需要花费大量的时间和精力。为了解决这个问题&#xff0c;我想向…...

Echarts常见图表展示

一、折线图 1.1 堆叠折线图 const option {title: {text: 折线图,},tooltip: {trigger: axis},legend: {data: [张三, 李四, 王五],bottom: 10,},grid: {left: 3%,right: 4%,bottom: 10%,containLabel: true},xAxis: {type: category,boundaryGap: false,data: [Mon, Tue, We…...

PySpark机器学习实战案例

目录 PySpark机器学习库 分布式机器学习原理 PySpark架构设计 PySpark项目实战...

微软操作系统中,windows server 系列和windows 的区别

Windows Server和Windows Desktop&#xff08;即我们常说的Windows系统&#xff09;是Microsoft公司的两种操作系统产品&#xff0c;它们都基于Windows NT内核。两者在设计目标、功能和价格等方面存在显著的区别。 设计目标与功能 Windows Desktop系统主要针对个人用户和企业的…...

本地部署 Stable Diffusion XL 1.0 Gradio Demo WebUI

StableDiffusion XL 1.0 Gradio Demo WebUI 0. 先展示几张 StableDiffusion XL 生成的图片1. 什么是 Stable Diffusion XL Gradio Demo WebUI2. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 安装 Stable Diffusion XL Gradio Demo WebUI6. 启动 Stable Diffusion XL Gradi…...

模型法在初中物理中的实例与应用

摘要&#xff1a;模型法是初中物理解题的重要方法&#xff0c;它的优点有方便快捷&#xff0c;易于理解等。文章通过列举模型法在初中物理解题时应用的例子&#xff0c;与模型法在学习与生活中的实际应用&#xff0c;说明了模型法可用性高&#xff0c;易于理解&#xff0c;能让…...

el-table 设置行背景颜色 鼠标移入高亮问题处理

一、 设置行背景颜色 1. 需求描述 后端返回表格数据&#xff0c;有特定行数需要用颜色标识。类似于以下需求&#xff1a; 2. 解决方式 方式区别:row-class-name“tableRowClassName”已返回类名的形式设置样式&#xff0c;代码整洁&#xff0c;但是会鼠标高亮&#xff0c…...

嵌入式面试常见题目收藏(超总结)

​ 这篇文章来自很多博客主和其他网站的作者&#xff0c;如有侵权&#xff0c;联系必删 文章出处标注&#xff1a; https://blog.csdn.net/qq_44330858/article/details/128947083 ***如需PDF或者原稿可私信 *** ***如需PDF或者原稿可私信 *** ***如需PDF或者原稿可私信 *** 1.…...

error in file(out, “wt“): cannot open the connection

这个错误在提示我们&#xff1a; 文件无法打开链接&#xff0c;可能是以下原因之一&#xff1a; 文件不存在或者路径错误&#xff1b;文件正在被其他程序占用&#xff1b;没有足够的权限来访问该文件&#xff1b;硬盘内存不足&#xff1b; 可以尝试的方法&#xff1a; 可以检…...

Redis (一)消息订阅和发送测试

〇、redis 配置 1、概况 本文基于 Ubuntu20.04 云服务器配置Redis&#xff0c;且在本地进行 Redis 测试。 2、目录概况 一、配置文件 位于 /config/app.yml 中&#xff0c;目的用于 Redis 初始化&#xff1a; redis:addr: "39.104.**.28:6379"password: "p…...

区间预测 | MATLAB实现QRGRU门控循环单元分位数回归多输入单输出区间预测

区间预测 | MATLAB实现QRGRU门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRGRU门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 MATLAB实现QRGRU门控循环单元分位数回归分位数回归多输入单输出区间…...

Debian 12.1 “书虫 “发布,包含 89 个错误修复和 26 个安全更新

导读Debian 项目今天宣布&#xff0c;作为最新 Debian GNU/Linux 12 “书虫 “操作系统系列的首个 ISO 更新&#xff0c;Debian 12.1 正式发布并全面上市。 Debian 12.1 是在 Debian GNU/Linux 12 “书虫 “发布六周后推出的&#xff0c;目的是为那些希望在新硬件上部署操作系统…...

hadoop部署配置

端口名称 Hadoop2.x Hadoop3.x NameNode内部通信端口 8020 / 9000 8020 / 9000/9820 NameNode HTTP UI 50070 9870 MapReduce查看执行任务端口 8088 8088 历史服务器通信端口 19888 19888 端口名称Hadoop2.xHadoop3.xNameNode内部通信端口8020 / 90008020 / 9000/9820NameNode…...

文心一言 VS 讯飞星火 VS chatgpt (68)-- 算法导论6.5 7题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;68&#xff09;-- 算法导论6.5 7题 七、试说明如何使用优先队列来实现一个先进先出队列&#xff0c;以及如何使用优先队列来实现栈(队列和栈的定义见 10.1 节。) 文心一言&#xff1a; 优先队列是一种数据结构&#xff0c;其中…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

短视频矩阵系统文案创作功能开发实践,定制化开发

在短视频行业迅猛发展的当下&#xff0c;企业和个人创作者为了扩大影响力、提升传播效果&#xff0c;纷纷采用短视频矩阵运营策略&#xff0c;同时管理多个平台、多个账号的内容发布。然而&#xff0c;频繁的文案创作需求让运营者疲于应对&#xff0c;如何高效产出高质量文案成…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...