SpringFrameworkのスケジューリング機能と同時実行の抑制

SpringFrameworkには、起動しているアプリケーションから非同期の処理を実行できる機能が提供されており、非同期処理を実行する方法がいくつかあります。その中の1つである スケジューラー(@Scheduled)を使った定期実行の方法と、そのスケジューラーの多重起動の防止を行うShedLockを紹介します。

スケジューラー機能の使い方

SpringBootを使った設定は以下の通りです

  • アプリケーション起動クラスないしは設定クラスに @EnableSchedulingを付与する
  • 一定間隔で非同期実行をするメソッドに@Scheduledを付与する(Springのコンテキストに登録してあるクラスが対象。)

具体的なコード例は次の通りです。

Springのスケジューラ設定と実行クラスの例

アプリケーション起動クラスないしは設定クラスにて @EnableSchedulingを付与しておけば、あとはスケジュール実行したいメソッドに @Scheduled を付与するだけです。

@Scheduledに指定できる属性にはcrontabと同じ形式を設定するcronか、アプリケーション起動完了後からの実行間隔を指定する方法が選べます。この例では、10秒間隔で、registerNormal() メソッドが常に非同期で実行されます。
cronの指定では時刻のタイムゾーンも別途指定できます。省略時はアプリケーションが起動しているサーバのタイムゾーンが使われます。(※タイムゾーンが影響する設定、具体的には1時間単位で指定して実行する処理をScheduledで設定した内容だけでアプリケーションの機能が充足するかはアプリケーションの設計で判断する)

常に非同期で実行するということは

先程の例では、10秒間隔でメソッドが動作します。
このアプリケーションが単一のアプリケーションで動作していて、かつこのメソッドで実行される処理が常に10秒以内で完了すれば特に問題ないところですが、作成するアプリケーションの大きさによっては、複数のサーバインスタンスに同一のアプリケーションが配備されて運用するケースがあります。そうなると、時間指定をした @Scheduledは、複数のアプリケーションから同時に同じ処理実行されることになるでしょう。検索処理や状態監視なら良いのですが、何らかのデータ登録や更新がある場合は、アプリケーション内で排他処理を実施しなければ重複してしまいます。

解決方法の1つ :ShedLockの導入

複数実行している同一アプリケーションで排他処理をかんたんに実装できるライブラリに、https://github.com/lukas-krecan/ShedLock があります。これは複数実行されている @Scheduledに対して排他処理を追加するものです。使い方もSpringBootのアノテーションのようにするだけです。

ShedLockの導入に必要なもの

導入に必要なものは以下です

  • ShedLock本体(maven centralないしは前述したgithubのリポジトリから入手)
  • ShedLock用のデータベース(JDBCから接続できるRDBや、Redis、DynamoDBなど多数から選択可。SpringBootアプリケーションで定義済みデータソースから利用)
  • ShedLock用のDBテーブル1つ作成

これらを準備したあとは設定するだけです。

ShedLockの設定

Springの @Scheduled同様に、アプリケーション設定用アノテーションと、排他を実施したいスケジューラーで起動しているメソッドにアノテーションを付与します。

@EnableSchedulerLock で、ShedLockの利用を宣言します。このアノテーションの属性で、Scheduledで実行するスケジューリングしたメソッドの排他時間のデフォルト値を設定します。

ShedLockの設定を追加したApplicationクラス
ShedLockを適用したサンプルコード

スケジュール起動するメソッドのうち、排他を実施したいものに @SchedulerLock を付与します。name属性はアプリケーション内で唯一となるものをつけます。ShedLockはこのname属性に指定した名前で排他する処理であるかを認識し、メソッドの開始をしたと同時に排他を開始します。排他の終了はShedLockの設定で定義した排他時間を超過しているかで判断します。

排他時間の設定には2つあります。

lockAtLeastFor(またはlockAtLestForString):
排他開始から最低でも排他を続ける時間。メソッドが終了してもこの時間までは排他がかかり続ける。排他中はスケジューラからの起動はされずに破棄される

lockAtMostFor(またはlockAtMostForString):
排他開始から最大で排他を続ける時間。メソッドが終了していなくてもこの時間が到達した場合は排他が解除される

この2つの値は @EnableSchedulerLock のdefault設定を個別に上書きできます。

まとめ

アプリケーションレベルでかんたんにスケジューラと排他を実施できることがわかりました。大掛かりなスケジューリング機能やサービスを使わずとも実現できますので、まずは手軽に処理単位が小さいものに適用してみるのも良いかと思います。

サンプルソース

以下に公開しております。
https://github.com/A-pZ/scheduler-sample

サンプルではデータベースにMySQLを選んでいます。