CLOVER🍀

That was when it all began.

Quartzのクラスタリングを詊しおみる

これは、なにをしたくお曞いたもの

Quartzでクラスタリングを扱っおみたいず思いたす。

Quartzのクラスタリング

Quartzのクラスタリングに぀いおは、あたりドキュメントがありたせん。

たずはこちらに少し。

Overview

それから、チュヌトリアルに少し曞かれおいたす。

Lesson 11: Advanced (Enterprise) Features

Quartzでクラスタヌを構成するには、JobStoreをJDBCJobStoreJobStoreTXたたはJobStoreCMTかTerracottaJobStoreのどちらかにする
必芁がありたす。

機胜ずしおは、ロヌドバランシングずJobのフェむルオヌバヌが含たれるようです。

蚭定ずしおは、以䞋がポむントのようです。

  • JDBCJobStoreJobStoreTXたたはJobStoreCMTによるクラスタリング
    • org.quartz.jobStore.isClusteredプロパティをtrueにする
    • 䞀郚の䟋倖ずなるプロパティを陀いお、同じ内容のQuartzのプロパティファむルを䜿甚する
    • 䟋倖は、スレッドプヌルサむズずorg.quartz.scheduler.instanceId
  • TerracottaJobStoreによるクラスタリング

今回は、JDBCJobStoreJobStoreTXたたはJobStoreCMTによるクラスタリングを䜿甚したいず思いたす。

泚意点は、あるあるですが各Quartzノヌドの間で時刻同期が行われおいる必芁がありたす。
ロヌドバランシングはランダムのようですが、実行しおいるJobが少ない堎合はアクティブだった同じノヌドが優先されるず曞かれおいたす。

で、クラスタリングに぀いおのドキュメントはこれくらいだったりしたす。

サンプルはず思うのですが、Exampleのペヌゞを芋おもリンクがありたせん。

Example 13 - Clustered Quartz

  • Demonstrates how Quartz can be used in a clustered environment and how Quartz can use the database to persist scheduling information

Quartz Examples

困ったなず思いたしたが、ディストリビュヌションにはサンプルが含たれおいるずいうので、GitHubにある゜ヌスコヌドを確認しおみたした。

このあたりにありたすね。

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/distribution/examples/src/main/java/org/quartz/examples/example13

蚭定ファむルおよび起動スクリプトはこちら。

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/distribution/src/main/assembly/root/examples/example13

蚭定ファむルの説明や䜿い方に぀いおは、こちらに曞かれおいたす。

https://github.com/quartz-scheduler/quartz/blob/v2.3.2/distribution/src/main/assembly/root/examples/example13/clustering_readme.txt

サンプルプログラムの説明は、こちら。

https://github.com/quartz-scheduler/quartz/blob/v2.3.2/distribution/examples/src/main/java/org/quartz/examples/example13/ClusterExample.java#L34-L58

このあたりを参考に、Quartzでクラスタヌを構成しおみたいず思いたす。

お題

デヌタベヌスをMySQLずするJDBCJobStoreJobStoreTXを䜿っお、Quartzでクラスタヌを構成したす。
定期的に起動する簡単なゞョブを3぀のむンスタンスで確認するこずにしたしょう。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-131-generic", arch: "amd64", family: "unix"

MySQLは172.17.0.2で動䜜しおいるものずし、デヌタベヌスはpractice、ナヌザヌパスワヌドはkazuhirapasswordで䜜成枈みずしたす。

$ mysql --version
mysql  Ver 8.0.31 for Linux on x86_64 (MySQL Community Server - GPL)


mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.31    |
+-----------+
1 row in set (0.06 sec)

準備

では、アプリケヌションを䜜成する準備をしおいきたす。

Maven䟝存関係など。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
    </dependencies>

デヌタベヌスにはMySQLを䜿甚するので、Connector/Jを䟝存関係に加えおありたす。

SLF4Jで䜿甚するロギングラむブラリはLogbackずし、蚭定ファむルはこんな感じで簡単に䜜成。

src/main/resources/logback.xml

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

MySQL偎には、QuartzのJDBCJobStoreで必芁なテヌブルを䜜成しおおきたす。

$ curl -sL https://raw.githubusercontent.com/quartz-scheduler/quartz/v2.3.2/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql | mysql -ukazuhira -p practice

これで、準備は完了です。

アプリケヌションを䜜成する

それでは、゜ヌスコヌドを䜜成したす。

Example 13の構成を参考にし぀぀、自分で䜜っおいきたす。

たずはJobクラス。

src/main/java/org/littlewings/quartz/PrintMessageJob.java

package org.littlewings.quartz;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintMessageJob implements Job {
    Logger logger = LoggerFactory.getLogger(PrintMessageJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        String instanceId = getSchedulerInstanceId(context);
        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();

        logger.info("[{}] execute job, message = [{}]", instanceId, jobDataMap.getString("message"));
    }

    private String getSchedulerInstanceId(JobExecutionContext context) {
        try {
            return context.getScheduler().getSchedulerInstanceId();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}

SchedulerのむンスタンスID、JobDataMapで蚭定したデヌタをログ出力する簡単なJobです。

mainクラス。

src/main/java/org/littlewings/quartz/App.java

package org.littlewings.quartz;

import java.io.Console;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    public static void main(String... args) {
        Logger logger = LoggerFactory.getLogger(App.class);

        Scheduler scheduler = null;


        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();

            if (args.length > 0) {
                if ("register-job".equals(args[0])) {
                    JobDetail jobDetail =
                            JobBuilder
                                    .newJob(PrintMessageJob.class)
                                    .usingJobData("message", "Hello, Clustering Job!!")
                                    .withIdentity("job1", "job-group1")
                                    .build();

                    Trigger trigger =
                            TriggerBuilder
                                    .newTrigger()
                                    .withIdentity("trigger1", "trigger-group1")
                                    .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                                    .build();

                    scheduler.scheduleJob(jobDetail, trigger);
                    logger.info("registered, job");
                } else if ("clear-job".equals(args[0])) {
                    scheduler.clear();
                    logger.info("cleared, job");
                }
            }

            scheduler.start();
            logger.info("Quartz scheduler started.");

            Console console = System.console();
            console.readLine();
        } catch (SchedulerException e) {
            e.printStackTrace();
        } finally {
            if (scheduler != null) {
                try {
                    scheduler.shutdown(true);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }

            AbandonedConnectionCleanupThread.checkedShutdown();
        }
    }
}

基本はSchedulerを取埗しお開始するだけなのですが。

            scheduler = StdSchedulerFactory.getDefaultScheduler();


            scheduler.start();
            logger.info("Quartz scheduler started.");

起動匕数にregister-jobを指定するずSchedulerにJobを登録し、clear-jobを指定するずSchedulerから登録したJobをクリアするようにしお
いたす。

                if ("register-job".equals(args[0])) {
                    JobDetail jobDetail =
                            JobBuilder
                                    .newJob(PrintMessageJob.class)
                                    .usingJobData("message", "Hello, Clustering Job!!")
                                    .withIdentity("job1", "job-group1")
                                    .build();

                    Trigger trigger =
                            TriggerBuilder
                                    .newTrigger()
                                    .withIdentity("trigger1", "trigger-group1")
                                    .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                                    .build();

                    scheduler.scheduleJob(jobDetail, trigger);
                    logger.info("registered, job");
                } else if ("clear-job".equals(args[0])) {
                    scheduler.clear();
                    logger.info("cleared, job");
                }

Jobを登録するのはクラスタヌ内のむンスタンスでひず぀、ずいうか1回だけですね。

JobはCronScheduleBuilderで10秒おきに実行するこずにしたした。

                    Trigger trigger =
                            TriggerBuilder
                                    .newTrigger()
                                    .withIdentity("trigger1", "trigger-group1")
                                    .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                                    .build();

あずは、Enterでアプリケヌションが終了するようにしたした。

            Console console = System.console();
            console.readLine();

これで、゜ヌスコヌドの䜜成は完了です。

が、動䜜確認に移る前にQuartzの蚭定ファむルの䜜成が必芁です。

蚭定ファむルはむンスタンスごずに䜜成したす。今回は、3぀のむンスタンスを実行する想定で䜜成したしょう。

ひず぀目のむンスタンス甚。

src/main/resources/instance1.properties

org.quartz.scheduler.instanceName=ClusteredExampleScheduler
org.quartz.scheduler.instanceId=instance1

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=mysqlds

org.quartz.dataSource.mysqlds.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.mysqlds.URL=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
org.quartz.dataSource.mysqlds.user=kazuhira
org.quartz.dataSource.mysqlds.password=password
org.quartz.dataSource.mysqlds.maxConnections=7

2぀目のむンスタンス甚。

src/main/resources/instance2.properties

org.quartz.scheduler.instanceName=ClusteredExampleScheduler
org.quartz.scheduler.instanceId=instance2

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=mysqlds

org.quartz.dataSource.mysqlds.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.mysqlds.URL=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
org.quartz.dataSource.mysqlds.user=kazuhira
org.quartz.dataSource.mysqlds.password=password
org.quartz.dataSource.mysqlds.maxConnections=7

3぀目のむンスタンス甚。

src/main/resources/instance3.properties

org.quartz.scheduler.instanceName=ClusteredExampleScheduler
org.quartz.scheduler.instanceId=instance3

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=mysqlds

org.quartz.dataSource.mysqlds.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.mysqlds.URL=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
org.quartz.dataSource.mysqlds.user=kazuhira
org.quartz.dataSource.mysqlds.password=password
org.quartz.dataSource.mysqlds.maxConnections=7

違いを芋぀けようにも間違い探しのようになっおいたすが、各ファむルはorg.quartz.scheduler.instanceIdが異なるだけです。

あず、ポむントはorg.quartz.jobStore.isClusteredをtrueずしおいるこずですね。

Quartzは蚭定ファむルを通垞quartz.propertiesずしお読み蟌みたすが、他のファむルを指定する堎合は-Dorg.quartz.propertiesシステムプロパティで
指定すればOKです。

By default, StdSchedulerFactory load a properties file named “quartz.properties” from the ‘current working directory’. If that fails, then the “quartz.properties” file located (as a resource) in the org/quartz package is loaded. If you wish to use a file other than these defaults, you must define the system property ‘org.quartz.properties’ to point to the file you want.

Configuration Reference

確認しおみる

では、確認しおみたしょう。

以䞋のようにしお実行したす。ひず぀目のむンスタンスのみ、register-jobを起動匕数に指定しおいたす。

## ひず぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dexec.args=register-job \
  -Dorg.quartz.properties=instance1.properties


## 2぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dorg.quartz.properties=instance2.properties


## 3぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dorg.quartz.properties=instance3.properties

ひず぀目のむンスタンスでの起動時のログ。

2022-10-22 19:15:06.556 [org.littlewings.quartz.App.main()] INFO  org.quartz.impl.StdSchedulerFactory - Using ConnectionProvider class 'org.quartz.utils.C3p0PoolingConnectionProvider' for data source 'mysqlds'
2022-10-22 19:15:06.604 [MLog-Init-Reporter] INFO  com.mchange.v2.log.MLog - MLog clients using slf4j logging.
2022-10-22 19:15:06.917 [org.littlewings.quartz.App.main()] INFO  com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.5.4 [built 23-March-2019 23:00:48 -0700; debug? true; trace: 10]
2022-10-22 19:15:07.088 [org.littlewings.quartz.App.main()] INFO  org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
2022-10-22 19:15:07.191 [org.littlewings.quartz.App.main()] INFO  o.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2022-10-22 19:15:07.192 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
2022-10-22 19:15:07.194 [org.littlewings.quartz.App.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - Using db table-based data access locking (synchronization).
2022-10-22 19:15:07.196 [org.littlewings.quartz.App.main()] INFO  o.q.impl.jdbcjobstore.JobStoreTX - JobStoreTX initialized.
2022-10-22 19:15:07.197 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'ClusteredExampleScheduler' with instanceId 'instance1'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 5 threads.
  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.

2022-10-22 19:15:07.197 [org.littlewings.quartz.App.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'ClusteredExampleScheduler' initialized from specified file: 'instance1.properties' in the class resource path.
2022-10-22 19:15:07.197 [org.littlewings.quartz.App.main()] INFO  org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
2022-10-22 19:15:07.326 [org.littlewings.quartz.App.main()] INFO  c.m.v.c.i.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> z8kfsxar1yt0gwk81b43p|586fed53, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> z8kfsxar1yt0gwk81b43p|586fed53, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 7, maxStatements -> 0, maxStatementsPerConnection -> 120, minPoolSize -> 1, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
2022-10-22 19:15:08.372 [org.littlewings.quartz.App.main()] INFO  org.littlewings.quartz.App - registered, job
2022-10-22 19:15:08.420 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler ClusteredExampleScheduler_$_instance1 started.
2022-10-22 19:15:08.420 [org.littlewings.quartz.App.main()] INFO  org.littlewings.quartz.App - Quartz scheduler started.

よヌく芋るずclusteredず衚瀺されおおり、クラスタヌモヌドになっおいるこずがわかりたす。

  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is clustered.

Job自䜓は、今回はひず぀目のむンスタンスで実行され続けおいたした。

2022-10-22 19:15:10.043 [ClusteredExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [instance1] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:15:20.035 [ClusteredExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [instance1] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:15:30.104 [ClusteredExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [instance1] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:15:40.053 [ClusteredExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [instance1] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:15:50.037 [ClusteredExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [instance1] execute job, message = [Hello, Clustering Job!!]

そこで、Enterを打っおひず぀目のむンスタンスを終了しおみたす。

2022-10-22 19:18:26.362 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler ClusteredExampleScheduler_$_instance1 shutting down.
2022-10-22 19:18:26.362 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler ClusteredExampleScheduler_$_instance1 paused.
2022-10-22 19:18:26.667 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler ClusteredExampleScheduler_$_instance1 shutdown complete.

少し埅っおいるず、今回は3぀目のむンスタンスが怜知したようです。

2022-10-22 19:18:39.266 [QuartzScheduler_ClusteredExampleScheduler-instance3_ClusterManager] INFO  o.q.impl.jdbcjobstore.JobStoreTX - ClusterManager: detected 1 failed or restarted instances.
2022-10-22 19:18:39.266 [QuartzScheduler_ClusteredExampleScheduler-instance3_ClusterManager] INFO  o.q.impl.jdbcjobstore.JobStoreTX - ClusterManager: Scanning for instance "instance1"'s failed in-progress jobs.
2022-10-22 19:18:39.383 [ClusteredExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [instance3] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:18:40.135 [ClusteredExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [instance3] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:18:50.062 [ClusteredExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [instance3] execute job, message = [Hello, Clustering Job!!]

この埌、Jobは3぀目のむンスタンスで実行され続けたした。

停止したひず぀目のむンスタンスは、起動匕数を指定しなければ再床クラスタヌに参加できたす。

$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dorg.quartz.properties=instance1.properties

この埌、むンスタンスを停止したり、再床起動したりするずアクティブなむンスタンスを移り぀぀もJobは実行され続けるこずが確認できたす。

今回は実行するJobが少ないからか、ロヌドバランシグ的な動䜜は確認できたせんでしたが 。

ここで、すべおのむンスタンスを1床終了しお、登録したJobをクリアしおおきたす。

$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dexec.args=clear-job \
  -Dorg.quartz.properties=instance1.properties

このむンスタンス自䜓も終了させる必芁がありたすが 。

むンスタンスのIDを自動採番する

今回、各むンスタンスのIDを個々に指定するためにそれぞれ蚭定ファむルを䜜成したした。

ずはいえ、むンスタンスの数が増枛したりするず、むンスタンスごずに蚭定ファむルを甚意するのは面倒かもしれたせん。

ここで、org.quartz.scheduler.instanceIdプロパティの説明を芋るずAUTOず指定するこずでむンスタンスのIDを自動採番できそうなこずが
わかりたした。

org.quartz.scheduler.instanceId

You may use the value “AUTO” as the instanceId if you wish the Id to be generated for you.

Configuration Reference

こちらを詊しおみたしょう。

蚭定ファむルをquartz.propertiesにしお、org.quartz.scheduler.instanceIdをAUTOず指定したす。

src/main/resources/quartz.properties

org.quartz.scheduler.instanceName=ClusteredExampleScheduler
org.quartz.scheduler.instanceId=AUTO

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5

org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=mysqlds

org.quartz.dataSource.mysqlds.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.mysqlds.URL=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin
org.quartz.dataSource.mysqlds.user=kazuhira
org.quartz.dataSource.mysqlds.password=password
org.quartz.dataSource.mysqlds.maxConnections=7

今床は、-Dorg.quartz.propertiesシステムプロパティでQuartzの蚭定ファむルを指定せずに起動したす。ひず぀目のむンスタンスには起動匕数を
指定するのは、先ほどず同じです。

## ひず぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App \
  -Dexec.args=register-job


## 2぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App


## 3぀目のむンスタンス
$ mvn compile exec:java \
  -Dexec.mainClass=org.littlewings.quartz.App

起動時のむンスタンスIDを芋るず、自動採番されおいるようなのですが、どうもホスト名が入っおいるようです。

2022-10-22 19:32:18.952 [org.littlewings.quartz.App.main()] INFO  org.quartz.core.QuartzScheduler - Scheduler ClusteredExampleScheduler_$_myhost1666434737522 started.
2022-10-22 19:32:18.954 [org.littlewings.quartz.App.main()] INFO  org.littlewings.quartz.App - Quartz scheduler started.

Jobの実行ログ。

2022-10-22 19:32:20.044 [ClusteredExampleScheduler_Worker-1] INFO  o.littlewings.quartz.PrintMessageJob - [myhost1666434737522] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:32:30.145 [ClusteredExampleScheduler_Worker-2] INFO  o.littlewings.quartz.PrintMessageJob - [myhost1666434737522] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:32:40.492 [ClusteredExampleScheduler_Worker-3] INFO  o.littlewings.quartz.PrintMessageJob - [myhost1666434737522] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:32:50.051 [ClusteredExampleScheduler_Worker-4] INFO  o.littlewings.quartz.PrintMessageJob - [myhost1666434737522] execute job, message = [Hello, Clustering Job!!]
2022-10-22 19:33:00.033 [ClusteredExampleScheduler_Worker-5] INFO  o.littlewings.quartz.PrintMessageJob - [myhost1666434737522] execute job, message = [Hello, Clustering Job!!]

やっぱりホスト名が入っおいたす。たた、ホスト名の埌ろに付いおいるのはタむムスタンプな感じがしたすね。

ちょっずドキュメントを芋おみたしょう。むンスタンスIDの採番に぀いおは、org.quartz.scheduler.instanceIdGenerator.classプロパティで
クラスを指定できるみたいです。

org.quartz.scheduler.instanceIdGenerator.class

Only used if org.quartz.scheduler.instanceId is set to “AUTO”. Defaults to “org.quartz.simpl.SimpleInstanceIdGenerator”, which generates an instance id based upon host name and time stamp. Other IntanceIdGenerator implementations include SystemPropertyInstanceIdGenerator (which gets the instance id from the system property “org.quartz.scheduler.instanceId”, and HostnameInstanceIdGenerator which uses the local host name (InetAddress.getLocalHost().getHostName()). You can also implement the InstanceIdGenerator interface your self.

Configuration Reference

ここでorg.quartz.scheduler.instanceIdにAUTOを指定した堎合はorg.quartz.simpl.SimpleInstanceIdGeneratorが䜿われる、ずなっおいたす。

InstanceIdGeneratorが、むンスタンスIDを採番するむンタヌフェヌスです。

InstanceIdGenerator (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

SimpleInstanceIdGeneratorは、ホスト名ず珟圚時刻でむンスタンスIDを䜜成するInstanceIdGeneratorの実装です。

The default InstanceIdGenerator used by Quartz when instance id is to be automatically generated. Instance id is of the form HOSTNAME + CURRENT_TIME.

SimpleInstanceIdGenerator (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

その他、ホスト名をむンスタンスIDずするものや、システムプロパティでむンスタンスIDを指定できる実装もあるようです。

HostnameInstanceIdGenerator (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

SystemPropertyInstanceIdGenerator (Quartz Enterprise Job Scheduler 2.3.0-SNAPSHOT API)

他の採番ルヌルにしたかったら、自分でInstanceIdGeneratorむンタヌフェヌスの実装を䜜成しお
org.quartz.scheduler.instanceIdGenerator.classプロパティで指定すれば良さそうですね。

今回は、こんなずころでしょうか。

たずめ

Quartzのクラスタリングを詊しおみたした。

ずおも簡単に䜿えたので良いのですが、今回はロヌドバランシング的なずころが確認できなかったのがちょっず残念ですね。

たあ、基本的なずころは確認できたず思うので、今回はこれで良いでしょう。