時限タスクの実装: タイマー、クォーツ

JDK におけるタイマーの使用法と原理

タイマーの使用

1 回限りのタスクのスケジュール設定

指定された遅延後に実行

@Test
public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect() {
    
    
    TimerTask task = new TimerTask() {
    
    
        public void run() {
    
    
            System.out.println("Task performed on: " + new Date() + "n" +
              "Thread's name: " + Thread.currentThread().getName());
        }
    };
    Timer timer = new Timer("Timer");
    
    long delay = 1000L;
    timer.schedule(task, delay);
}

指定時刻に実行
2 番目のパラメータが Data の場合、指定された日付に実行することを意味します。
ここに画像の説明を挿入
古いレガシー データベースがあり、そのデータをより優れたスキーマを持つ新しいデータベースに移行したいとします。その移行を処理するための DatabaseMigrationTask クラスを作成できます。

public class DatabaseMigrationTask extends TimerTask {
    
    
    private List<String> oldDatabase;
    private List<String> newDatabase;

    public DatabaseMigrationTask(List<String> oldDatabase, List<String> newDatabase) {
    
    
        this.oldDatabase = oldDatabase;
        this.newDatabase = newDatabase;
    }

    @Override
    public void run() {
    
    
        newDatabase.addAll(oldDatabase);
    }
}

簡単にするために、両方のデータベースを文字列のリストとして表します。簡単に言えば、私たちの移行は、最初のリストのデータを 2 番目のリストに入れることです。この移行を希望のタイミングで実行するには、オーバーロードされたバージョンのSchedule() メソッドを使用する必要があります。

List<String> oldDatabase = Arrays.asList("Harrison Ford", "Carrie Fisher", "Mark Hamill");
List<String> newDatabase = new ArrayList<>();

LocalDateTime twoSecondsLater = LocalDateTime.now().plusSeconds(2);
Date twoSecondsLaterAsDate = Date.from(twoSecondsLater.atZone(ZoneId.systemDefault()).toInstant());

new Timer().schedule(new DatabaseMigrationTask(oldDatabase, newDatabase), twoSecondsLaterAsDate);

移行タスクと実行日をschedule()メソッドに渡します。次に、twoSecondsLater で示される時刻に移行を実行します。

while (LocalDateTime.now().isBefore(twoSecondsLater)) {
    
    
    assertThat(newDatabase).isEmpty();
    Thread.sleep(500);
}
assertThat(newDatabase).containsExactlyElementsOf(oldDatabase);

繰り返し可能なタスクのスケジュール設定

Timer クラスにはいくつかの可能性があります。固定遅延または固定周波数を観察するように繰り返しを設定できます。

  • 固定遅延: 遅延した場合でも (つまり、実行自体が遅延した場合でも)、最後の実行が開始されてからしばらくしてから実行が開始されることを意味します。タスクを 2 秒ごとにスケジュールするとします。最初の実行には 1 秒かかり、2 番目の実行には 2 秒かかりますが、1 秒の遅延が発生します。そして、5秒目から3回目の実行が開始されます。

秒ごとのニュースレターをスケジュールしてみましょう。

public class NewsletterTask extends TimerTask {
    
    
    @Override
    public void run() {
    
    
        System.out.println("Email sent at: " 
          + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()), 
          ZoneId.systemDefault()));
    }
}

タスクが実行されるたびに、スケジュールされた時刻が出力されます。この時刻は、TimerTask#scheduledExecutionTime() メソッドを使用して収集します。では、固定遅延モードでこのタスクを毎秒スケジュールしたい場合はどうすればよいでしょうか? 前述したオーバーロードされたバージョンのSchedule()を使用する必要があります。

new Timer().schedule(new NewsletterTask(), 0, 1000);

for (int i = 0; i < 3; i++) {
    
    
    Thread.sleep(1000);
}

もちろん、テストするのは少数のケースだけです。

メールの送信先: 2020-01-01T10:50:30.860
メールの送信先: 2020-01-01T10:50:31.860
メールの送信先: 2020-01-01T10:50:32.861
メールの送信先: 2020-01-01T10:50 :33.861

各実行の間には少なくとも 1 秒の間隔がありますが、ミリ秒の遅延が発生する場合もあります。この動作は、固定の遅延で繰り返すという決定によるものです。

  • 固定頻度: 前回の実行が遅延したかどうかに関係なく、各実行が初期スケジュールに従うことを意味します。前の例を再利用しましょう。固定頻度で、2 番目のタスクは 3 秒後に開始されます (遅延のため)。ただし、3 回目の実行は 4 秒後です (2 秒ごとに実行するという初期スケジュールに関して)。

毎日のタスクをスケジュールするには:

@Test
public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect() {
    
    
    TimerTask repeatedTask = new TimerTask() {
    
    
        public void run() {
    
    
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    
    long delay = 1000L;
    long period = 1000L * 60L * 60L * 24L;
    timer.scheduleAtFixedRate(repeatedTask, delay, period);
}

スケジューラーとタスクをキャンセルする

TimerTask 自体の run() メソッドの実装で TimerTask.cancel() メソッドを呼び出します。

@Test
public void givenUsingTimer_whenCancelingTimerTask_thenCorrect()
  throws InterruptedException {
    
    
    TimerTask task = new TimerTask() {
    
    
        public void run() {
    
    
            System.out.println("Task performed on " + new Date());
            cancel();
        }
    };
    Timer timer = new Timer("Timer");
    
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    
    Thread.sleep(1000L * 2);
}

タイマーをキャンセルします:
Timer.cancel() を呼び出します。

@Test
public void givenUsingTimer_whenCancelingTimer_thenCorrect() 
  throws InterruptedException {
    
    
    TimerTask task = new TimerTask() {
    
    
        public void run() {
    
    
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    
    Thread.sleep(1000L * 2); 
    timer.cancel(); 
}

タイミングタスクのスレッドプール

クォーツフレームワーク

Quartz は、ジョブ (タスク)、トリガー (トリガー)、およびスケジューラー (スケジューラー) を定義するだけでスケジュールされたスケジューリング機能を実現する軽量のタスク スケジューリング フレームワークです。タスクの冪等実行を実現できるデータベースベースのクラスターモードをサポートします。

コアクラスの説明

ここに画像の説明を挿入

  • スケジューラー: スケジューラー。すべてのスケジュールはそれによって制御され、それは Quartz の頭脳であり、すべてのタスクはそれによって管理されます
  • ジョブ: タスク、定期的に実行したいこと (ビジネス ロジックの定義)
  • JobDetail: ジョブに基づいて、ジョブを関連付け、識別などのジョブのより詳細な属性を指定するさらなるパッケージ化。
  • トリガー:トリガー。タスクに割り当てることができ、タスクのトリガーメカニズムを指定します

トリガートリガー

シンプルトリガー

一定の時間間隔(ミリ秒単位)で実行されるタスク

  • 開始時刻と終了時刻を指定する(終了時刻)
  • 時間間隔、実行回数を指定する

ここに画像の説明を挿入
ここに画像の説明を挿入

CronTrigger (強調)

Cron 式は、主にタイミング ジョブ (時限タスク) システムで実行時間または実行頻度の式を定義するために使用されます。Cron 式を使用して、タイミング タスクを毎日または毎月実行する時刻を設定できます。
cron 式の形式:

{秒} {分} {時間} {日付} {月} {週} {年(空でも可)}

Cron 式に基づいて構築された CronTrigger は、SimpleTrigger の startNow()、endAt() などを使用しなくなり、withSchedule(CronScheduleBuilder.cronSchedule("30 01 17 11 5 ?")) のみを使用するようになりました。

ここに画像の説明を挿入
これは、5 月 11 日の毎時、毎分、毎秒が 1 回実行されることを意味します。

CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1","group1")
               		 .withSchedule(CronScheduleBuilder.cronSchedule(  "* * * 11 5 ?"))
              		 .build();

5 月 11 日の毎分および 2 秒ごとに実行

CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1","group1")
               		 .withSchedule(CronScheduleBuilder.cronSchedule(  "*/2 * * 11 5 ?"))
               		 .build();

スプリングにはクォーツが組み込まれています(強調)

タスク情報 SQL ストレージ

Quartz にタスク情報を保存するには、メモリを使用する方法とデータベースを使用して保存する方法があります。ここでは MySQL データベースの保存方法を使用します。まず、新しい Quartz 関連テーブルを作成する必要があります。SQL スクリプトのダウンロード アドレス: http:/ /www.quartz-scheduler.org /downloads/、名前は tables_mysql.sql です。作成が成功すると、データベースにはさらに 11 個のテーブルが存在します。

Maven は主に次のものに依存します

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 5.1.* 版本适用于MySQL Server的5.6.*、5.7.*和8.0.* -->
<dependency>
	<groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
	<version>5.1.38</version>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<!--mybatis-->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>
<!--pagehelper分页-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.3.0</version>
</dependency>

ここでは、druid がデータベース接続プールとして使用され、Quartz はデフォルトで c3p0 を使用します。

設定ファイル

1.quartz.properties
デフォルトでは、Quartz はクラスパスの下にquartz.propertiesを設定ファイルとしてロードします。見つからない場合は、quartz フレームワークの jar パッケージの org/quartz にあるquartz.properties ファイルが使用されます。

#主要分为scheduler、threadPool、jobStore、dataSource等部分


org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false


#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数  如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true


#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore

#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#数据源别名,自定义
org.quartz.jobStore.dataSource=qzDS


#使用阿里的druid作为数据库连接池
org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=123456
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.maxConnections=10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
#org.quartz.jobStore.isClustered=false

構成の詳細な説明: https://blog.csdn.net/zixiao217/article/details/53091812

公式 Web サイトも確認できます: http://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/

2、アプリケーションのプロパティ

server.port=8080

#JDBC 配置:MySQL Server 版本为 5.7.35
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

#druid 连接池配置
spring.datasource.druid.initial-size=3
spring.datasource.druid.min-idle=3
spring.datasource.druid.max-active=10
spring.datasource.druid.max-wait=60000

#指定 mapper 文件路径
mybatis.mapper-locations=classpath:org/example/mapper/*.xml
mybatis.configuration.cache-enabled=true
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
#打印 SQL 语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

Quartz 構成クラス

@Configuration
public class QuartzConfig implements SchedulerFactoryBeanCustomizer {
    
    

    @Bean
    public Properties properties() throws IOException {
    
    
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        // 对quartz.properties文件进行读取
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        // 在quartz.properties中的属性被读取并注入后再初始化对象
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
    
    
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setQuartzProperties(properties());
        return schedulerFactoryBean;
    }

    /*
     * quartz初始化监听器
     */
    @Bean
    public QuartzInitializerListener executorListener() {
    
    
        return new QuartzInitializerListener();
    }

    /*
     * 通过SchedulerFactoryBean获取Scheduler的实例
     */
    @Bean
    public Scheduler scheduler() throws IOException {
    
    
        return schedulerFactoryBean().getScheduler();
    }

    /**
     * 使用阿里的druid作为数据库连接池
     */
    @Override
    public void customize(@NotNull SchedulerFactoryBean schedulerFactoryBean) {
    
    
        schedulerFactoryBean.setStartupDelay(2);
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setOverwriteExistingJobs(true);
    }
}

ビジネス用途

ステップ 1: タスク クラスを作成します。

@Slf4j
public class HelloJob implements Job {
    
    

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
    
    
        QuartzService quartzService = (QuartzService) SpringUtil.getBean("quartzServiceImpl");
        PageInfo<JobAndTriggerDto> jobAndTriggerDetails = quartzService.getJobAndTriggerDetails(1, 10);
        log.info("任务列表总数为:" + jobAndTriggerDetails.getTotal());
        log.info("Hello Job执行时间: " + DateUtil.now());
    }
}

おすすめ

転載: blog.csdn.net/baiduwaimai/article/details/132050546