スケジューラーリファレンスガイド
最近のアプリケーションでは、定期的に特定のタスクを実行する必要があります。Quarkusには2つのスケジューラーエクステンションがあります。 quarkus-scheduler
エクステンションには、APIと軽量なインメモリースケジューラーの実装が含まれています。 quarkus-quartz
エクステンションは、 quarkus-scheduler
エクステンションのAPIを実装し、Quartzライブラリをベースにしたスケジューラーの実装が含まれています。 quarkus-quartz
が必要になるのは、永続的タスク、クラスターリング、ジョブのプログラマティックスケジューリングなど、より高度なスケジューリングのユースケースのみです。
プロジェクトに quarkus-quartz 依存関係を追加すると、 quarkus-scheduler エクステンションからの軽量スケジューラーの実装は自動的に無効になります。
|
1. スケジュールされたメソッド
メソッドに @io.quarkus.scheduler.Scheduled
をアノテーションすると、自動的に呼び出されるようにスケジュールされます。実際には、そのようなメソッドはCDI Beanの非プライベート非静的メソッドでなければなりません。CDI Beanのメソッドであることの結果として、スケジュールされたメソッドは、 @javax.transaction.Transactional
や @org.eclipse.microprofile.metrics.annotation.Counted
のようなインターセプターバインディングでアノテーションすることができます。
スコープを持たないBeanクラスで、 @Scheduled でアノテーションされた非静的メソッドが少なくとも一つ宣言されている場合、 @Singleton が使用されます。
|
さらに、アノテーションされたメソッドは void
を返し、パラメーターを宣言しないか、 io.quarkus.scheduler.ScheduledExecution
型のパラメーターを 1 つだけ宣言する必要があります。
アノテーションは繰り返し可能なので、1つのメソッドを複数回スケジュールすることができます。 |
サブクラスは、スーパークラスで宣言された
|
io.quarkus.scheduler.SuccessfulExecution
型の CDI イベントは、スケジュールされたメソッドの実行が成功したときに、同期および非同期で発生します。 io.quarkus.scheduler.FailedExecution
タイプの CDI イベントは、スケジュールされたメソッドの実行が例外をスローしたときに、同期および非同期で発生します。
1.1. トリガー
トリガーは、 @Scheduled#cron()
属性または @Scheduled#every()
属性によって定義されます。両方が指定されている場合、cron 式が優先されます。何も指定されていない場合、ビルドは IllegalStateException
で失敗します。
1.1.1. CRON
CRONトリガーは、cronライクな式で定義されています。例えば "0 15 10 * * ?"
は毎日午前10時15分に起動します。
@Scheduled(cron = "0 15 10 * * ?")
void fireAt10AmEveryDay() { }
CRON 式で使用される構文は quarkus.scheduler.cron-type
プロパティーによって制御されます。 値は cron4j
, quartz
, unix
および spring
のいづれかです。quartz
がデフォルトで使用されます。
cron
属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}" スタイルの表現もサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(cron = "${myMethod.cron.expr}")
void myMethod() { }
特定のスケジュールメソッドを無効にしたい場合は、そのcron式を "off"
または "disabled"
に設定します。
myMethod.cron.expr=disabled
プロパティ式では、そのプロパティが構成されていない場合に使用されるデフォルト値を定義することができます。
0 0 15 ? * MON *
のCRON設定プロパティーの例@Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
void myMethod() { }
プロパティ myMethod.cron.expr
が未定義または null
の場合は、デフォルト値( 0 0 15 ? * MON *
)が使用されます。
1.1.2. インターバル
インターバルトリガーは、呼び出しの間の期間を定義します。期間式は ISO-8601 の期間フォーマット PnDTnHnMn.nS
に基づいており、 @Scheduled#every()
の値は java.time.Duration#parse(CharSequence)
で解析されます。ただし、式が数字で始まる場合は、 PT
の接頭辞が自動的に追加されます。例えば、 PT15M
の代わりに 15m
を使用することができ、「15 分」と解析されます。
@Scheduled(every = "15m")
void every15Mins() { }
every
属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}"
スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(every = "${myMethod.every.expr}")
void myMethod() { }
Intervals は、その値を "off"
または "disabled"
に設定することで無効にすることができます。そのため、例えば、デフォルト値 "off"
のプロパティ式は、そのConfig Propertyが設定されていない場合、トリガーを無効にするために使用することができます。
@Scheduled(every = "${myMethod.every.expr:off}")
void myMethod() { }
1.2. アイデンティティ
デフォルトでは、スケジュールされた各メソッドに対して一意の ID が生成されます。このIDはログメッセージやデバッグ中に使用されます。時には、明示的な id を指定することが便利な場合もあります。
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }
identity
属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}"
スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(identity = "${myMethod.identity.expr}")
void myMethod() { }
1.3. 遅延実行
@Scheduled
では、トリガーが発報を開始時刻を遅らせるための2つの方法を提供しています。
@Scheduled#delay()
と @Scheduled#delayUnit()
は初期遅延を一緒に形成します。
@Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
void everyTwoSeconds() { }
1 | アプリケーション開始から2時間後に初めてトリガーが発射されます。 |
最終的な値は常に1秒単位で丸められます。 |
@Scheduled#delayed()
は、上記のプロパティーのテキスト形式での代替です。ピリオド式は ISO-8601 duration format PnDTnHnMn.nS
に基づいており、値は java.time.Duration#parse(CharSequence)
で解析されます。ただし、式が数字で始まる場合は、 PT
の接頭辞が自動的に追加されます。そのため、例えば PT15S
の代わりに 15s
を使用することができ、「15 秒」と解析されます。
@Scheduled(every = "2s", delayed = "2h")
void everyTwoSeconds() { }
@Scheduled#delay() がゼロよりも大きい値に設定されている場合、 @Scheduled#delayed() の値は無視されます。
|
@Scheduled#delay()
と比較した場合の主な利点は、値が設定可能であることです。 delay
属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式 をサポートしています。(なお、 "{property.path}"
スタイルの表現はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") (1)
void everyTwoSeconds() { }
1 | 遅延の設定には、configプロパティー myMethod.delay.expr を使用します。 |
1.4. 同時実行
デフォルトでは、スケジュールされたメソッドは同時に実行することができます。それにもかかわらず、 @Scheduled#concurrentExecution()
を通じて同時実行を処理するための戦略を指定することが可能です。
import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;
@Scheduled(every = "1s", concurrentExecution = SKIP) (1)
void nonConcurrent() {
// we can be sure that this method is never executed concurrently
}
1 | 同時実行はスキップされます。 |
スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
|
同じアプリケーションインスタンス内での実行のみが考慮されることに注意してください。この機能は、クラスター間での動作を意図したものではありません。 |
1.5. 条件付き実行
@Scheduled#skipExecutionIf()
を通じて、スケジュールされたメソッドの実行をスキップするロジックを定義することができます。指定されたBeanクラスは、 io.quarkus.scheduler.Scheduled.SkipPredicate
を実装する必要があり、 test()
メソッドの結果が true
である場合に実行がスキップされます。
class Jobs {
@Scheduled(every = "1s", skipExecutionIf = MyPredicate.class) (1)
void everySecond() {
// do something every second...
}
}
@Singleton (2)
class MyPredicate implements SkipPredicate {
@Inject
MyService service;
boolean test(ScheduledExecution execution) {
return !service.isStarted(); (3)
}
}
1 | MyPredicate.class の Bean インスタンスは、実行をスキップすべきかどうかを評価するために使用されます。Beanタイプのセットに指定されたクラスを持つBeanが正確に1つ存在する必要があり、そうでない場合はビルドが失敗します。 |
2 | Beanのスコープは,実行中にアクティブでなければならない。 |
3 | Jobs.everySecond() は、 MyService.isStarted() が true を返すまでスキップされます。 |
なお、これは以下のコードと同等のものです:
class Jobs {
@Inject
MyService service;
@Scheduled(every = "1s")
void everySecond() {
if (service.isStarted()) {
// do something every second...
}
}
}
主なアイデアは、スケジュールされたビジネスメソッドの外で実行をスキップするロジックを維持することで、再利用やリファクタリングが容易にできるようにすることです。
スケジュールされたメソッドの実行がスキップされると、 io.quarkus.scheduler.SkippedExecution のタイプのCDIイベントが発生します。
|
1.6. ノンブロッキング方式
デフォルトでは、スケジュールされたメソッドは、ブロックするタスクのメインエクゼキュータ上で実行されます。そのため、Vert.xイベントループ上で実行するように設計された技術(Hibernate Reactiveなど)は、メソッド本体内で使用することができません。このため、 java.util.concurrent.CompletionStage<Void>
または io.smallrye.mutiny.Uni<Void>
を返す、あるいは @io.smallrye.common.annotation.NonBlocking
でアノテーションされたスケジュール型メソッドは、代わりに Vert.x のイベントループで実行されます。
class Jobs {
@Scheduled(every = "1s")
Uni<Void> everySecond() { (1)
// ...do something async
}
}
1 | リターンタイプ Uni<Void> は、Vert.xのイベントループでメソッドを実行するようにスケジューラに指示します。 |
2. スケジューラー
Quarkusは、 io.quarkus.scheduler.Scheduler
型のビルトインBeanを提供しており、これを注入してスケジューラーを一時停止/再開するために使用することができます。
import io.quarkus.scheduler.Scheduler;
class MyService {
@Inject
Scheduler scheduler;
void ping() {
scheduler.pause(); (1)
scheduler.pause("myIdentity"); (2)
if (scheduler.isRunning()) {
throw new IllegalStateException("This should never happen!");
}
scheduler.resume("myIdentity"); (3)
scheduler.resume(); (4)
scheduler.getScheduledJobs(); (5)
Trigger jobTrigger = scheduler.getScheduledJob("myIdentity"); (6)
if (jobTrigger != null && jobTrigger.isOverdue()){ (7)
// the job is late to the party.
}
}
}
1 | すべてのトリガーを一時停止します。 |
2 | スケジュールされた特定のメソッドをそのIDで一時停止する |
3 | スケジュールされた特定のメソッドを、そのIDで再開する |
4 | スケジューラーを再開します。 |
5 | スケジューラーを再開します。 |
6 | スケジュールされた特定のメソッドを、そのIDで再開する。 |
7 | isOverdue()の猶予期間は、quarkus.scheduler.overdue-grace-periodを使用して設定することができます |
3. プログラムスケジューリング
プログラムでジョブをスケジュールする必要がある場合は、 Quartzエクステンション を追加してQuartz APIを直接使用する必要があります。
import org.quartz.Scheduler;
class MyJobs {
void onStart(@Observes StartupEvent event, Scheduler quartz) throws SchedulerException {
JobDetail job = JobBuilder.newJob(SomeJob.class)
.withIdentity("myJob", "myGroup")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "myGroup")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
quartz.scheduleJob(job, trigger);
}
}
デフォルトでは、 @Scheduled ビジネスメソッドが見つからない限り、スケジューラーは起動されません。「純粋な」プログラマティックスケジューリングのために、スケジューラーを強制的に起動する必要があるかもしれません。 Quartz設定リファレンス も参照してください。
|
4. スケジュールされたメソッドとテスト
テストを実行する際には、スケジューラーを無効にすることが望ましいことがよくあります。スケジューラーは、ランタイム設定プロパティー quarkus.scheduler.enabled
を通じて無効にすることができます。 false
に設定すると、アプリケーションにスケジュールされたメソッドが含まれていても、 スケジューラーは起動しません。特定の テストプロファイル に対してスケジューラーを無効にすることもできます。
5. メトリクス
quarkus.scheduler.metrics.enabled
が true
に設定されていて、metrics エクステンションが存在する場合、いくつかの基本的なメトリクスがすぐに公開されます。
Micrometer エクステンション が存在する場合、すべての @Scheduled
メソッドに @io.micrometer.core.annotation.Timed
インターセプターバインディングが自動的に追加され (すでに存在している場合を除く)、 scheduled.methods
という名前の io.micrometer.core.instrument.Timer
と scheduled.methods.running
という名前の io.micrometer.core.instrument.LongTaskTimer
が登録されます。タグとしては、宣言したクラスの完全修飾名と、 @Scheduled
のメソッド名が使われます。
SmallRye Metrics エクステンション が存在する場合、 @org.eclipse.microprofile.metrics.annotation.Timed
インターセプターバインディングがすべての @Scheduled
メソッドに自動的に追加され(すでに存在している場合を除く)、 org.eclipse.microprofile.metrics.Timer
が各 @Scheduled
メソッドに対して作成されます。名前は、宣言したクラスの完全修飾名と @Scheduled
メソッドの名前で構成されます。タイマーには、タグ scheduled=true
が付いています。
6. OpenTelemetry Tracing
If quarkus.scheduler.tracing.enabled
is set to true
and the OpenTelemetry extension is present then the @io.opentelemetry.instrumentation.annotations.WithSpan
annotation is added automatically to every @Scheduled
method. As a result, each execution of this method has a new io.opentelemetry.api.trace.Span
associated.
Non-blocking methods are not supported, i.e. a new span is associated with the actual invocation but it’s not available within the asynchronous computation. |
7. 設定リファレンス
ビルド時に固定される設定プロパティ - その他の設定プロパティはランタイムでオーバーライド可能です。
型 |
デフォルト |
|
---|---|---|
The syntax used in CRON expressions. Environment variable: |
|
|
Scheduled task metrics will be enabled if a metrics extension is present and this value is true. Environment variable: |
boolean |
|
Tracing will be enabled if the OpenTelemetry extension is present and this value is true. Environment variable: |
boolean |
|
If schedulers are enabled. Environment variable: |
boolean |
|
Scheduled task will be flagged as overdue if next execution time is exceeded by this period. Environment variable: |
|
期間フォーマットについて
期間のフォーマットは標準の 数値で始まる期間の値を指定することもできます。この場合、値が数値のみで構成されている場合、コンバーターは値を秒として扱います。そうでない場合は、 |