スケジューラーリファレンスガイド
最近のアプリケーションでは、特定のタスクを定期的に実行する必要があることがよくあります。Quarkusには、2つのスケジューラエクステンションがあります。 quarkus-scheduler
エクステンションは、APIと軽量のインメモリスケジューラー実装を提供します。 quarkus-quartz
エクステンションは、 quarkus-scheduler
エクステンションのAPIを実装し、Quartzライブラリに基づくスケジューラ実装を含んでいます。 quarkus-quartz
が必要になるのは、永続タスクやクラスタリングなど、より高度なスケジューリングのユースケースだけです。
プロジェクトに quarkus-quartz 依存関係を追加すると、 quarkus-scheduler エクステンションからの軽量スケジューラーの実装は自動的に無効になります。
|
1. スケジュールされたメソッド
@io.quarkus.scheduler.Scheduled
でアノテーションされたメソッドは、自動的に呼び出しがスケジュールされます。スケジュールされたメソッドは、abstractやprivateであってはなりません。静的または非静的であっても大丈夫です。スケジュールされたメソッドには、 @jakarta.transaction.Transactional
や @org.eclipse.microprofile.metrics.annotation.Counted
のようなインターセプターバインディングをアノテーションすることができます。
スコープを持たないBeanクラスで、 @Scheduled でアノテーションされた非静的メソッドが少なくとも一つ宣言されている場合、 @Singleton が使用されます。
|
さらに、アノテーションされたメソッドは void
を返し、パラメーターを宣言しないか、 io.quarkus.scheduler.ScheduledExecution
型のパラメーターを 1 つだけ宣言する必要があります。
アノテーションは繰り返し可能なので、1つのメソッドを複数回スケジュールすることができます。 |
1.1. Inheritance of metadata
A subclass never inherits the metadata of a @Scheduled
method declared on a superclass.
For example, suppose the class org.amce.Foo
is extended by the class org.amce.Bar
.
If Foo
declares a non-static method annotated with @Scheduled
then Bar
does not inherit the metadata of the scheduled method.
In the following example, the everySecond()
method is only invoked upon the instance of Foo
.
class Foo {
@Scheduled(every = "1s")
void everySecond() {
// ..do something
}
}
@Singleton
class Bar extends Foo {
}
1.2. CDI events
Some CDI events are fired synchronously and asynchronously when specific events occur.
タイプ | Event description |
---|---|
|
An execution of a scheduled job completed successfully. |
|
An execution of a scheduled job completed with an exception. |
|
An execution of a scheduled job was skipped. |
|
The scheduler was paused. |
|
The scheduler was resumed. |
|
A scheduled job was paused. |
|
A scheduled job was resumed. |
1.3. トリガー
トリガーは、 @Scheduled#cron()
属性または @Scheduled#every()
属性によって定義されます。両方が指定されている場合、cron 式が優先されます。何も指定されていない場合、ビルドは IllegalStateException
で失敗します。
1.3.1. CRON
CRONトリガーは、cronライクな式で定義されています。例えば "0 15 10 * * ?"
は毎日午前10時15分に起動します。
@Scheduled(cron = "0 15 10 * * ?")
void fireAt1015AmEveryDay() { }
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.3.1.1. タイムゾーン
cron式は、デフォルトのタイムゾーンのコンテキストで評価されます。しかし、cron式を特定のタイムゾーンに関連付けることも可能です。
@Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") (1)
void myMethod() { }
1 | タイムゾーンIDは、 java.time.ZoneId#of(String) を使って解析されます。 |
The timeZone
attribute supports Property Expressions including default values and nested
Property Expressions.
@Scheduled(cron = "0 15 10 * * ?", timeZone = "{myMethod.timeZone}")
void myMethod() { }
1.3.2. インターバル
An interval trigger defines a period between invocations.
The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS
and the value of @Scheduled#every()
is parsed with java.time.Duration#parse(CharSequence)
.
However, if an expression starts with a digit and ends with d
, P
prefix will be added automatically. If the expression only starts with a digit, PT
prefix is added automatically.
So for example, 15m
can be used instead of PT15M
and is parsed as "15 minutes".
@Scheduled(every = "15m")
void every15Mins() { }
A value less than one second may not be supported by the underlying scheduler implementation. In that case a warning message is logged during build and application start. |
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.4. アイデンティティ
デフォルトでは、スケジュールされた各メソッドに対して一意の ID が生成されます。このIDはログメッセージやデバッグ中に使用されます。時には、明示的な id を指定することが便利な場合もあります。
@Scheduled(identity = "myScheduledMethod")
void myMethod() { }
identity
属性は、デフォルト値やネストされたプロパティ式を含む プロパティ式をサポートしています。(なお、 "{property.path}"
スタイル式はサポートされていますが、プロパティ式の完全な機能は提供されていません)。
@Scheduled(identity = "${myMethod.identity.expr}")
void myMethod() { }
1.5. Delayed Start of a Trigger
@Scheduled
では、トリガーが発報を開始時刻を遅らせるための2つの方法を提供しています。
@Scheduled#delay()
と @Scheduled#delayUnit()
は初期遅延を一緒に形成します。
@Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
void everyTwoSeconds() { }
1 | アプリケーション開始から2時間後に初めてトリガーが発射されます。 |
最終的な値は常に1秒単位で丸められます。 |
@Scheduled#delayed()
is a text alternative to the properties above.
The period expression is based on the ISO-8601 duration format PnDTnHnMn.nS
and the value is parsed with java.time.Duration#parse(CharSequence)
.
However, if an expression starts with a digit and ends with d
, P
prefix will be added automatically. If the expression only starts with a digit, PT
prefix is added automatically.
So for example, 15s
can be used instead of PT15S
and is parsed as "15 seconds".
@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.6. 遅延実行
@Scheduled#executionMaxDelay()
can be set to delay each execution of a scheduled method.
The value represents the maximum delay between the activation of the trigger and the execution of the scheduled method.
The actual delay is a randomized number between 0 and the maximum specified delay.
The value is parsed with DurationConverter#parseDuration(String)
.
It can be a property expression, in which case, the scheduler attempts to use the configured value instead: @Scheduled(executionMaxDelay = "${myJob.maxDelay}")
.
Additionally, the property expression can specify a default value:
@Scheduled(executionMaxDelay = "${myJob.maxDelay}:500ms}")
.
@Scheduled(every = "2s", executionMaxDelay = "500ms") (1)
void everyTwoSeconds() { }
1 | The delay will be a value between 0 and 500 milliseconds. As a result, the period between to everyTwoSeconds() executions will be roughly between one and a half and two and a half seconds. |
1.7. 同時実行
デフォルトでは、スケジュールされたメソッドは同時に実行することができます。それにもかかわらず、 @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.8. 条件付き実行
You can define the logic to skip any execution of a scheduled method via @Scheduled#skipExecutionIf()
.
The specified class must implement io.quarkus.scheduler.Scheduled.SkipPredicate
and the execution is skipped if the result of the test()
method is true
.
The class must either represent a CDI bean or declare a public no-args constructor.
In case of CDI, there must be exactly one bean that has the specified class in its set of bean types, otherwise the build fails.
Furthermore, the scope of the bean must be active during execution of the job.
If the scope is @Dependent
then the bean instance belongs exclusively to the specific scheduled method and is destroyed when the application is shut down.
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イベントが発生します。
|
To skip the scheduled executions while the application is starting up/shutting down, you can make use of the io.quarkus.scheduler.Scheduled.ApplicationNotRunning skip predicate.
|
1.9. ノンブロッキング方式
デフォルトでは、スケジュールされたメソッドは、ブロックするタスクのメインエクゼキュータ上で実行されます。そのため、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のイベントループでメソッドを実行するようにスケジューラに指示します。 |
1.10. How to use multiple scheduler implementations
In some cases, it might be useful to choose a scheduler implementation used to execute a scheduled method.
However, only one Scheduler
implementation is used for all scheduled methods by default.
For example, the quarkus-quartz
extension provides an implementation that supports clustering but it also removes the simple in-memory implementation from the game.
Now, if clustering is enabled then it’s not possible to define a scheduled method that would be executed locally on a single node.
Nevertheless, if you set the quarkus.scheduler.use-composite-scheduler
config property to true
then a composite Scheduler
is used instead.
This means that multiple scheduler implementations are kept running side by side.
Furthermore, it’s possible to chose a specific implementation used to execute a scheduled method using @Scheduled#executeWith()
.
class Jobs {
@Scheduled(cron = "0 15 10 * * ?") (1)
void fireAt10AmEveryDay() { }
@Scheduled(every = "1s", executeWith = Scheduled.SIMPLE) (2)
void everySecond() { }
}
1 | If the quarkus-quartz extension is present then this method will be executed with the Quartz-specific scheduler. |
2 | If quarkus.scheduler.use-composite-scheduler=true is set then this method will be executed with the simple in-memory implementation provided by the quarkus-scheduler extension. |
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を使用して設定することができます |
CDIイベントは、スケジューラーやスケジュールされたジョブが一時停止/再開されたときに、同期および非同期で発生します。ペイロードはそれぞれ io.quarkus.scheduler.SchedulerPaused , io.quarkus.scheduler.SchedulerResumed , io.quarkus.scheduler.ScheduledJobPaused , io.quarkus.scheduler.ScheduledJobResumed です。
|
3. プログラムスケジューリング
注入された io.quarkus.scheduler.Scheduler
は、プログラム的にジョブをスケジュールするために使用することもできます。
import io.quarkus.scheduler.Scheduler;
@ApplicationScoped
class MyJobs {
@Inject
Scheduler scheduler;
void addMyJob() { (1)
scheduler.newJob("myJob")
.setCron("0/5 * * * * ?")
.setTask(executionContext -> { (2)
// do something important every 5 seconds
})
.schedule(); (3)
}
void removeMyJob() {
scheduler.unscheduleJob("myJob"); (4)
}
}
1 | これは、 @Scheduled(identity = "myJob", cron = "0/5 * * * * ?") でアノテーションされたメソッドに代わるプログラム的な代替です。 |
2 | ビジネスロジックはコールバックで定義されます。 |
3 | JobDefinition#schedule() メソッドが呼び出されると、ジョブがスケジュールされます。 |
4 | プログラムによって追加されたジョブも削除することができます。 |
デフォルトでは、 @Scheduled ビジネスメソッドが見つからない限り、スケジューラは開始されません。「純粋な」プログラムによるスケジューリングでは、 quarkus.scheduler.start-mode=forced を使ってスケジューラを強制的に起動させる必要があるかもしれません。
|
If the Quartz extension is present and the DB store type is used then it’s not possible to pass a task instance to the job definition and a task class must be used instead. The Quartz API can be also used to schedule a job programmatically. |
In certain cases, a more fine-grained approach might be needed which is why Quarkus also exposes java.util.concurrent.ScheduledExecutorService
and java.util.concurrent.ExecutorService
that can be injected as CDI beans.
However, these executors are used by other Quarkus extensions and therefore should be approached with caution. Furthermore, users are never allowed to shut these executors down manually.
class JobScheduler {
@Inject
ScheduledExecutorService executor;
void everySecondWithDelay() {
Runnable myRunnable = createMyRunnable();
executor.scheduleAtFixedRate(myRunnable, 3, 1, TimeUnit.SECONDS);
}
}
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 every job execution, either defined with the @Scheduled
annotation or scheduled programmatically, automatically creates a span named after the job’s アイデンティティ.
7. 仮想スレッド上で @Scheduled メソッドを実行
@Scheduled
でアノテーションされたメソッドは、 @RunOnVirtualThread
でアノテーションすることもできます。
この場合、メソッドは仮想スレッド上で呼び出されます。
メソッドは void
を返す必要があり、Java ランタイムは仮想スレッドのサポートを提供する必要があります。
詳細については、 仮想スレッドガイド をお読みください。
8. 設定リファレンス
ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能
Configuration property |
型 |
デフォルト |
---|---|---|
The syntax used in CRON expressions. Environment variable: Show more |
|
|
Scheduled task metrics will be enabled if a metrics extension is present and this value is true. Environment variable: Show more |
boolean |
|
Controls whether tracing is enabled. If set to true and the OpenTelemetry extension is present, tracing will be enabled, creating automatic Spans for each scheduled task. Environment variable: Show more |
boolean |
|
By default, only one Scheduler implementations will be started depending on the value of Environment variable: Show more |
boolean |
|
If schedulers are enabled. Environment variable: Show more |
boolean |
|
Scheduled task will be flagged as overdue if next execution time is exceeded by this period. Environment variable: Show more |
|
|
Scheduler can be started in different modes. By default, the scheduler is not started unless a Environment variable: Show more |
|
期間フォーマットについて
To write duration values, use the standard 数字で始まる簡略化した書式を使うこともできます:
その他の場合は、簡略化されたフォーマットが解析のために
|