大家好,我是二哥呀。定时任务的应用场景其实蛮常见的,比如说:

  • 数据备份
  • 订单未支付则自动取消
  • 定时爬取数据
  • 定时推送信息
  • 定时发布文章
  • 等等(想不出来了,只能等等来凑,,反正只要等的都需要定时,怎么样,这波圆场可以吧)

编程喵实战项目里需要做一个定时发布文章的功能,一开始我想用 Spring Task,于是研究了一番,发现 Spring Task 用起来确实简单,但对于复杂业务却也无能为力。

于是我就把注意力放到了 Quartz 上面,这是一款老而弥坚的开源任务调度框架。

记得我在 14 年开发大宗期货交易平台的时候就用到了它,每天凌晨定时需要统计一波交易数据,生成日报报表,当时配合 Cron 表达式用的。

可惜后来平台稳定了,新的政策出来了,直接把大宗期货交易灭了。于是我发财的机会也随着破灭了。想想都觉得可惜,哈哈哈。

时光荏苒,Quartz 发展到现在,已经可以和 Spring Boot 项目无缝衔接了,今天我们就来实战一把。

Timer

JDK 1.3 就开始支持的一种定时任务的实现方式。内部通过 TaskQueue 的类来存放定时任务,用起来比较简单,但缺陷比较多,比如说一个 Timer 就会起一个线程,任务多了性能就非常差,再比如说如果执行任务期间某个 TimerTask 耗时比较久,就会影响其他任务的调度。

@Slf4j
public class TimerDemo {
public static void main(String[] args) {
TimerTask task = new TimerTask() {
@Override
public void run() {
log.debug("当前时间{}线程名称{}", DateTime.now(),
Thread.currentThread().getName());
}
};
log.debug("当前时间{}线程名称{}", DateTime.now(),
Thread.currentThread().getName());
Timer timer = new Timer("TimerDemo");
timer.schedule(task,1000L);
}
}

代码跑起来后的日志如下所示:

13:11:45.268 [main] DEBUG top.springtask.TimerDemo - 当前时间2022-04-27 13:11:45线程名称main
13:11:46.280 [TimerDemo] DEBUG top.springtask.TimerDemo - 当前时间2022-04-27 13:11:46线程名称TimerDemo

ScheduledThreadPoolExecutor

JDK 1.5 开始提供的的定时任务,它继承了 ThreadPoolExecutor,实现了 ScheduledExecutorService 接口,所以支持并发场景下的任务执行。同时,优化了 Timer 的缺陷。不过,由于使用了队列来实现定时器,就有出入队列、调整堆等操作,所以定时不是非常非常准确(吹毛求疵)。

@Slf4j
public class ScheduledThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException {
TimerTask task = new TimerTask() {
@Override
public void run() {
log.debug("当前时间{}线程名称{}", DateTime.now(),
Thread.currentThread().getName());
}
}; log.debug("当前时间{}线程名称{}", DateTime.now(),
Thread.currentThread().getName());
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.scheduleAtFixedRate(task, 1000L,1000L, TimeUnit.MILLISECONDS);
Thread.sleep(1000+1000*4);
executorService.shutdown();
}
}

输出结果如下所示:

14:43:41.740 [main] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:41线程名称main
14:43:42.752 [pool-1-thread-1] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:42线程名称pool-1-thread-1
14:43:43.748 [pool-1-thread-1] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:43线程名称pool-1-thread-1
14:43:44.749 [pool-1-thread-2] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:44线程名称pool-1-thread-2
14:43:45.749 [pool-1-thread-2] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:45线程名称pool-1-thread-2
14:43:46.749 [pool-1-thread-2] DEBUG top.springtask.ScheduledThreadPoolExecutorDemo - 当前时间2022-04-27 14:43:46线程名称pool-1-thread-2

Spring Task

Spring Task 是 Spring 提供的轻量级定时任务工具,也就意味着不需要再添加第三方依赖了,相比其他第三方类库更加方便易用。

好像关于 Spring Task,没有其他废话可说了,我们来直接上手。

第一步,新建配置类 SpringTaskConfig,并添加 @EnableScheduling注解开启 Spring Task。

@Configuration
@EnableScheduling
public class SpringTaskConfig {
}

当然了,也可以不新建这个配置类,直接在主类上添加 @EnableScheduling 注解。

@SpringBootApplication
@EnableScheduling
public class CodingmoreSpringtaskApplication { public static void main(String[] args) {
SpringApplication.run(CodingmoreSpringtaskApplication.class, args);
} }

第二步,新建定时任务类 CronTask,使用 @Scheduled 注解注册 Cron 表达式执行定时任务。

@Slf4j
@Component
public class CronTask {
@Scheduled(cron = "0/1 * * ? * ?")
public void cron() {
log.info("定时执行,时间{}", DateUtil.now());
}
}

启动服务器端,发现每隔一秒钟会打印一次日志,证明 Spring Task 的 cron 表达式形式已经起效了。

默认情况下,@Scheduled 创建的线程池大小为 1,如果想增加线程池大小的话,可以让 SpringTaskConfig 类实现 SchedulingConfigurer 接口,通过 setPoolSize 增加线程池大小。

@Configuration
@EnableScheduling
public class SpringTaskConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10);
threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
threadPoolTaskScheduler.initialize(); taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}

服务热部署完成后,会在控制台看到这样的信息:

可以确认自定义线程池大小已经生效了,有的任务用的是线程led-task-pool-3,有的是线程led-task-pool-7,跑时间长了,可以发现 led-task-pool-1 到 led-task-pool-10 的都有。

Spring Task 除了支持 Cron 表达式,还有 fixedRate(固定速率执行)、fixedDelay(固定延迟执行)、initialDelay(初始延迟)三种用法。

/**
* fixedRate:固定速率执行。每5秒执行一次。
*/
@Scheduled(fixedRate = 5000)
public void reportCurrentTimeWithFixedRate() {
log.info("Current Thread : {}", Thread.currentThread().getName());
log.info("Fixed Rate Task : The time is now {}", DateUtil.now());
} /**
* fixedDelay:固定延迟执行。距离上一次调用成功后2秒才执。
*/
@Scheduled(fixedDelay = 2000)
public void reportCurrentTimeWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task : The time is now {}",DateUtil.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
} /**
* initialDelay:初始延迟。任务的第一次执行将延迟5秒,然后将以5秒的固定间隔执行。
*/
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public void reportCurrentTimeWithInitialDelay() {
log.info("Fixed Rate Task with Initial Delay : The time is now {}", DateUtil.now());
}

不过,fixedRate 有个坑,假如某个方法的定时器设定的固定速率是每5秒执行一次,这个方法现在要执行下面四个任务,四个任务的耗时是:6s、6s、 2s、 3s,任务会如何执行呢(单线程环境下)?

2022-04-27 15:25:52.400  INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:25:52
2022-04-27 15:25:58.401 INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:25:58
2022-04-27 15:26:00.407 INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:26:00
2022-04-27 15:26:04.318 INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:26:04

第一个任务开始的相对时间是第 0 秒,但由于执行了 6 秒,所以原来应该是第 5 秒执行的任务,延迟到第 6 秒才开始执行,第三个任务延迟了 12 秒,原本应该是第 10 秒执行,第三个任务没有延迟,正常 15 秒后执行。

假如我们使用 @EnableAsync 注解开启多线程环境的话,结果会怎么样呢?

2022-04-27 15:33:01.385  INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:33:01
2022-04-27 15:33:07.390 INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:33:07
2022-04-27 15:33:09.391 INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:33:09
2022-04-27 15:33:13.295 INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask : Fixed Rate Task : The time is now 2022-04-27 15:33:13

关于 Cron 表达式

这里顺带普及一下 Cron 表达式,在定时任务中会经常会遇到。Cron 这个词来源于希腊语 chronos,原意也就是时间。

Cron 表达式是一个含有时间意义的字符串,以 5 个空格隔开,分成 6 个时间元素。举几个例子就一目了然了。

示例 说明
0 15 10 ? * * 每天上午10:15执行任务
0 0 10,14,16 * * ? 每天10 点、14 点、16 点执行任务
0 0 12 ? * 3 每个星期三中午 12 点执行任务
0 15 10 15 * ? 每月 15 日上午 10 点 15 执行任务

Cron 的语法格式可以总结为:

Seconds Minutes Hours DayofMonth Month DayofWeek

每个时间元素的取值范围,以及可出现的特殊字符如下所示。

时间元素 取值范围 可出现的特殊字符
[0,59] *,-/
分钟 [0,59] *,-/
小时 [0,59] *,-/
日期 [0,31] *,-/?LW
月份 [1,12] *,-/
星期 [1,7] *,-/?L#

特殊字符的含义和示例如下所示。

特殊字符 含义 示例
* 所有可能的值 很好理解,月域中为每个月,星期域中每个星期几
, 枚举的值 很好理解,小时域中 10,14,16,就表示这几个小时可选
- 范围 很好理解,分钟域中 10-19,就表示 10-19 分钟每隔一分钟执行一次
/ 指定数值的增量 很好理解,分钟域中 0/15,就表示每隔 15 分钟执行一次
? 不指定值 很好理解,日期域指定了星期域就不能指定值,反之亦然,因为日期域和星期域属于冲突关系
L 单词 Last 的首字母 很好理解,日期域和星期域支持,表示月的最后一天或者星期的最后一天
W 除周末以外的工作日 很好理解,仅日期域支持
# 每个月的第几个星期几 很好理解,仅星期域支持,4#2表示某月的第二个星期四

关于 Quartz

Quartz 是一款功能强大的开源的任务调度框架,在 GitHub 上已经累计有 5k+ 的 star 了。小到单机应用,大到分布式,都可以整合 Quartz。

在使用 Quartz 之前,让我们先来搞清楚 4 个核心概念:

  • Job:任务,要执行的具体内容。
  • JobDetail:任务详情,Job 是它要执行的内容,同时包含了这个任务调度的策略和方案。
  • Trigger:触发器,可以通过 Cron 表达式来指定任务执行的时间。
  • Scheduler:调度器,可以注册多个 JobDetail 和 Trigger,用来调度、暂停和删除任务。

整合 Quartz

Quartz 存储任务的方式有两种,一种是使用内存,另外一种是使用数据库。内存在程序重启后就丢失了,所以我们这次使用数据库的方式来进行任务的持久化。

第一步,在 pom.xml 文件中添加 Quartz 的 starter。

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

第二步,在 application.yml 添加 Quartz 相关配置,配置说明直接看注释。

spring:
quartz:
job-store-type: jdbc # 默认为内存 memory 的方式,这里我们使用数据库的形式
wait-for-jobs-to-complete-on-shutdown: true # 关闭时等待任务完成
overwrite-existing-jobs: true # 可以覆盖已有的任务
jdbc:
initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构
properties: # quartz原生配置
org:
quartz:
scheduler:
instanceName: scheduler # 调度器实例名称
instanceId: AUTO # 调度器实例ID自动生成
# JobStore 相关配置
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用完全兼容JDBC的驱动
tablePrefix: QRTZ_ # Quartz 表前缀
useProperties: false # 是否将JobDataMap中的属性转为字符串存储
# 线程池相关配置
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool # 指定线程池实现类,对调度器提供固定大小的线程池

Quartz 默认使用的是内存的方式来存储任务,为了持久化,我们这里改为 JDBC 的形式,并且指定 spring.quartz.jdbc.initialize-schema=never,这样我们可以手动创建数据表。因为该值的另外两个选项ALWAYS和EMBEDDED都不太符合我们的要求:

  • ALWAYS:每次都初始化
  • EMBEDDED:只初始化嵌入式数据库,比如说 H2、HSQL

那手动创建数据表的 SQL 语句去哪里找呢?

GitHub 地址:https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore

为了方便小伙伴们下载,我把它放在了本教程的源码里面了:

如果使用 Intellij IDEA 旗舰版的话,首次打开 SQL 文件的时候会提示你指定数据源。在上图中,我配置了本地的 MySQL 数据库,导入成功后可以在数据库中查看到以下数据表:

Quartz数据库核心表如下:

Table Name Description
QRTZ_CALENDARS 存储Quartz的Calendar信息
QRTZ_CRON_TRIGGERS 存储CronTrigger,包括Cron表达式和时区信息
QRTZ_FIRED_TRIGGERS 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组的信息
QRTZ_SCHEDULER_STATE 存储少量的有关Scheduler的状态信息,和别的Scheduler实例
QRTZ_LOCKS 存储程序的悲观锁的信息
QRTZ_JOB_DETAILS 存储每一个已配置的Job的详细信息
QRTZ_JOB_LISTENERS 存储有关已配置的JobListener的信息
QRTZ_SIMPLE_TRIGGERS 存储简单的Trigger,包括重复次数、间隔、以及已触的次数
QRTZ_BLOG_TRIGGERS Trigger作为Blob类型存储
QRTZ_TRIGGER_LISTENERS 存储已配置的TriggerListener的信息
QRTZ_TRIGGERS 存储已配置的Trigger的信息

剩下的就是对 Quartz 的 scheduler、jobStore 和 threadPool 配置。

第三步,创建任务调度的接口 IScheduleService,定义三个方法,分别是通过 Cron 表达式来调度任务、指定时间来调度任务,以及取消任务。

public interface IScheduleService {
/**
* 通过 Cron 表达式来调度任务
*/
String scheduleJob(Class<? extends Job> jobBeanClass, String cron, String data); /**
* 指定时间来调度任务
*/
String scheduleFixTimeJob(Class<? extends Job> jobBeanClass, Date startTime, String data); /**
* 取消定时任务
*/
Boolean cancelScheduleJob(String jobName);
}

第四步,创建任务调度业务实现类 ScheduleServiceImpl,通过Scheduler、CronTrigger、JobDetail的API来实现对应的方法。

@Slf4j
@Service
public class ScheduleServiceImpl implements IScheduleService {
private String defaultGroup = "default_group"; @Autowired
private Scheduler scheduler;
@Override
public String scheduleJob(Class<? extends Job> jobBeanClass, String cron, String data) {
String jobName = UUID.fastUUID().toString();
JobDetail jobDetail = JobBuilder.newJob(jobBeanClass)
.withIdentity(jobName, defaultGroup)
.usingJobData("data", data)
.build();
//创建触发器,指定任务执行时间
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, defaultGroup)
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 调度器进行任务调度
try {
scheduler.scheduleJob(jobDetail, cronTrigger);
} catch (SchedulerException e) {
log.error("任务调度执行失败{}", e.getMessage());
}
return jobName;
} @Override
public String scheduleFixTimeJob(Class<? extends Job> jobBeanClass, Date startTime, String data) {
//日期转CRON表达式
String startCron = String.format("%d %d %d %d %d ? %d",
DateUtil.second(startTime),
DateUtil.minute(startTime),
DateUtil.hour(startTime, true),
DateUtil.dayOfMonth(startTime),
DateUtil.month(startTime) + 1,
DateUtil.year(startTime));
return scheduleJob(jobBeanClass, startCron, data);
} @Override
public Boolean cancelScheduleJob(String jobName) {
boolean success = false;
try {
// 暂停触发器
scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
// 移除触发器中的任务
scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
// 删除任务
scheduler.deleteJob(new JobKey(jobName, defaultGroup));
success = true;
} catch (SchedulerException e) {
log.error("任务取消失败{}", e.getMessage());
}
return success;
}
}

第五步,定义好要执行的任务,继承 QuartzJobBean 类,实现

executeInternal 方法,这里只定义一个定时发布文章的任务。

@Slf4j
@Component
public class PublishPostJob extends QuartzJobBean {
@Autowired
private IScheduleService scheduleService;
@Autowired
private IPostsService postsService; @Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Trigger trigger = jobExecutionContext.getTrigger();
JobDetail jobDetail = jobExecutionContext.getJobDetail();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
Long data = jobDataMap.getLong("data");
log.debug("定时发布文章操作:{}",data); // 获取文章的 ID后获取文章,更新文章为发布的状态,还有发布的时间
boolean success = postsService.updatePostByScheduler(data); //完成后删除触发器和任务
if (success) {
log.debug("定时任务执行成功,开始清除定时任务");
scheduleService.cancelScheduleJob(trigger.getKey().getName());
}
}
}

第六步,发布文章的接口里 PostsServiceImpl 添加定时发布的任务调度方法。

@Service
public class PostsServiceImpl extends ServiceImpl<PostsMapper, Posts> implements IPostsService { private void handleScheduledAfter(Posts posts) {
// 文章已经保存为草稿了,并且拿到了文章 ID
// 调用定时任务
String jobName = scheduleService.scheduleFixTimeJob(PublishPostJob.class, posts.getPostDate(), posts.getPostsId().toString());
LOGGER.debug("定时任务{}开始执行", jobName);
} }

好,我们现在启动服务,通过Swagger 来测试一下,注意设置文章的定时发布时间。

查看 Quartz 的数据表 qrtz_cron_triggers,发现任务已经添加进来了。

qrtz_job_details 表里也可以查看具体的任务详情。

文章定时发布的时间到了之后,在日志里也可以看到 Quartz 的执行日志。

再次查看 Quartz 数据表 qrtz_cron_triggers 和 qrtz_job_details 的时候,也会发现定时任务已经清除了。

整体上来说,Spring Boot 整合 Quartz还是非常丝滑的,配置少,步骤清晰,比 Spring Task 更强大,既能针对内存也能持久化,所以大家在遇到定时任务的时候完全可以尝试一把。

完整的功能在编程喵实战项目中已经实现了,可以把编程喵导入到本地尝试一下。

业务梳理

简单来梳理一下编程喵定时发布文章的业务。

1)用户在发布文章的时候可以选择定时发布,如果选择定时发布,那么就要设置定时发布的时间,暂时规定至少十分钟以后可以定时。

2)当管理端用户选择了定时发布,那么在保存文章的时候,文章状态要先设置为草稿状态,对前端用户是不可见的状态。

3)保存文章的时候通知 Quartz,我有一个任务,你需要在某个规定的时间去执行。

scheduleService.scheduleFixTimeJob(PublishPostJob.class, posts.getPostDate(), posts.getPostsId().toString());

4)Quartz 收到这个通知后,就会在数据库中写入任务,具体的任务是到指定时间把文章从草稿的状态转为发布状态,这时候,前端用户就可以看得见文章了。

// 获取文章的 ID后获取文章,更新文章为发布的状态,还有发布的时间
boolean success = postsService.updatePostByScheduler(data);

同时,将任务清除。

// 暂停触发器
scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
// 移除触发器中的任务
scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
// 删除任务
scheduler.deleteJob(new JobKey(jobName, defaultGroup));

整个过程就完成了。Quartz 是如何实现定时发布文章的呢?其实也是通过 Cron 表达式。

CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, defaultGroup)
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();

也就是当我们传入一个指定时间后,通过计算,计算出 Cron 表达式。

String startCron = String.format("%d %d %d %d %d ? %d",
DateUtil.second(startTime),
DateUtil.minute(startTime),
DateUtil.hour(startTime, true),
DateUtil.dayOfMonth(startTime),
DateUtil.month(startTime) + 1,
DateUtil.year(startTime));

在 Quartz 中,有两类线程:Scheduler调度线程和任务执行线程。

  • 任务执行线程:Quartz不会在主线程(QuartzSchedulerThread)中处理用户的Job。Quartz把线程管理的职责委托给ThreadPool,一般的设置使用SimpleThreadPool。SimpleThreadPool创建了一定数量的WorkerThread实例来使得Job能够在线程中进行处理。WorkerThread是定义在SimpleThreadPool类中的内部类,它实质上就是一个线程。
  • QuartzSchedulerThread调度主线程:QuartzScheduler被创建时创建一个QuartzSchedulerThread实例。

源码路径

本文已收录到 GitHub 上星标 2.4k+ 的开源专栏《Java 程序员进阶之路》,据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。学 Java,就认准 Java 程序员进阶之路

https://github.com/itwanger/toBeBetterJavaer

star 了这个仓库就等于你拥有了成为了一名优秀 Java 工程师的潜力。该开源仓库最近又上 GitHub trending 榜单了,看来是大家都非常认可呀!

没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟

SpringBoot官方支持任务调度框架,轻量级用起来也挺香!的更多相关文章

  1. .NET Core下开源任务调度框架Hangfire的Api任务拓展(支持秒级任务)

    HangFire的拓展和使用 看了很多博客,小白第一次写博客. 最近由于之前的任务调度框架总出现问题,因此想寻找一个替代品,之前使用的是Quartz.Net,这个框架方便之处就是支持cron表达式适合 ...

  2. SpringBoot整合任务调度框架Quartz及持久化配置

    目录 本篇要点 SpringBoot与Quartz单机版快速整合 引入依赖 创建Job 调度器Scheduler绑定 自动配置,这里演示SimpleScheduleBuilder 手动配置,这里演示C ...

  3. DDD实战进阶第一波(四):开发一般业务的大健康行业直销系统(搭建支持DDD的轻量级框架三)

    上一篇文章我们讲了经典DDD架构对比传统三层架构的优势,以及经典DDD架构每一层的职责后,本篇文章将介绍基础结构层中支持DDD的轻量级框架的主要代码. 这里需要说明的是,DDD轻量级框架能够体现DDD ...

  4. DDD实战进阶第一波(三):开发一般业务的大健康行业直销系统(搭建支持DDD的轻量级框架二)

    了解了DDD的好处与基本的核心组件后,我们先不急着进入支持DDD思想的轻量级框架开发,也不急于直销系统需求分析和具体代码实现,我们还少一块, 那就是经典DDD的架构,只有了解了经典DDD的架构,你才能 ...

  5. Quarzt.NET 任务调度框架

      Quartz.NET是一个开源的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,它用C#写成,可用于winform和asp.net应用中.它提供了巨大的灵活性 ...

  6. 068 Oozie任务调度框架

    一:概述 1.大数据协作框架 2.Hadoop的任务调度 这个是常见的任务调度框架. 3.azkaban 4..Oozie的三大功能 Oozie Workflow jobs :工作流任务,可以生成DA ...

  7. 开源的.NET任务调度框架-HangFire

    什么是Hangfire Hangfire 是一个开源的.NET任务调度框架,目前1.6+版本已支持.NET Core.内置提供集成化的控制台,方便后台查看及监控: 另外,Hangfire包含三大核心组 ...

  8. Java任务调度框架Quartz入门

    Quartz[kwɔːts]:石英,其框架和名字一样简单朴素又不失魅力,在Java程序界,Quartz大名鼎鼎,很多Java应用几乎都集成或构建了一个定时任务调度系统,Quartz是一个定时任务调度框 ...

  9. SpringBoot系列之日志框架使用教程

    目录 1.SpringBoot日志级别 1).日志级别简介 2).默认日志级别 3).配置日志级别 4).日志分组设置 2.SpringBoot日志格式设置 1).默认格式原理简介 2).默认日志格式 ...

随机推荐

  1. PAT B1014 福尔摩斯约会

    大侦探福尔摩斯接到一张奇怪的字条:我们约会吧! 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk d&Hyscvnm.大侦探很快就明白了,字条上奇 ...

  2. in a frame because it set 'X-Frame-Options' to 'sameorigin'

    不是所有网站都给  iframe嵌套的,  有的网站设置了  禁止嵌套!!! 浏览器会依据X-Frame-Options的值来控制iframe框架的页面是否允许加载显示出来, 看你们公司这么设置了!! ...

  3. Netty初见-三大组件-简单使用

    Netty系列文章目录 Netty初见-三大组件-简单使用 文件编程-更新中---- 目录 Netty系列文章目录 三大组件 Channel与Buffer Selector 简单使用(ByteBuff ...

  4. eBPF+Ftrace 合璧剑指:no space left on device?

    本文地址:https://www.ebpf.top/post/no_space_left_on_devices 最近在生产环境中遇到了几次创建容器报错 "no space left on d ...

  5. 得到一个a(10)到b(20)的随机数。包括10和20

  6. Angular项目构建指南 - 不再为angular构建而犹豫不决(转)

    如果你不知道什么是Angular或者根本没听说过,那么我接下来所说的对你来说毫无益处,不过如果你打算以后会接触Angular或者干脆要涨涨姿势~读下去还是有点用的. Angular和它之前所出现的其余 ...

  7. 我的树莓派 Raspberry Pi Zero 2W 折腾笔记,记录一些遇到的问题和解决办法

    0x00 前言说明 最近买了一块Raspberry Pi Zero 2W来玩,目的是想搭建一台远程运行的个人服务器,上面放个博客.点个灯啥的.于是就有了这篇文章. 树莓派官网地址:https://ww ...

  8. 【kubernetes 问题排查】使用 kubeadm 部署时遇到的问题

    引言 再使用kubeadm部署集群时会多少遇到一些问题,这里做下记录,方便后面查找问题时有方向,同时也为刚要入坑的你指明下方向,让你少走点弯路 问题汇总 The connection to the s ...

  9. XCTF练习题---MISC---适合作为桌面

    XCTF练习题---适合作为桌面 flag:flag{38a57032085441e7} 解题步骤: 1.观察题目,下载附件 2.拿到题目以后是一张图片,切换一下通道,发现一张二维码,使用QR进行翻译 ...

  10. ViewFlipper 简单应用

    AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphi ...