2014.12.20
Play Framework(Play2)(Scala)のTips Part3:Akkaを使った非同期/バッチ処理(Advent Calendar 20日目)
この記事はアライドアーキテクツAdvent Calendar 20日目の記事です。
どうも。伊藤(係長)です。
今日は他の方に乗っ取られる事もなく無事に書くことができます。
今回はAkkaを使った非同期処理やバッチ処理についてです。
通常、非同期処理をJavaとかで書こうと思ったら、ちょっとメンドクサイですよね。
ところがAkkaを使えば比較的簡単に書く事ができるのです。
※私自身がこの辺の処理が苦手なので、深いところまでは全く踏み込みません。
(というか踏み込めません)
■Akkaとは
Actorによる並列処理を行う為のライブラリ。
ScalaとJavaのAPIがあり、並列処理、分散処理を簡単に記述する事が可能です。
詳しくはAkkaをご覧ください。
単純な非同期処理
事前準備
Playに最初から組み込まれている為、特に必要ありません。
実装サンプル
Akka.system.schedulerを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package controllers import play.api.libs.concurrent.Akka import play.api.mvc.{Action, Controller} import scala.concurrent.duration._ object Sample extends Controller { def doSomething() = Action { doMain() // 何かメインの処理 Akka.system.scheduler.scheduleOnce(1.seconds) { doAsync() // サブ的な処理 } Ok("hoge") } } |
これだけです。これだけでOk(“hoge”)が先に実行されてView側に結果を返し、
裏ではdoAsyncが1秒後に実行されます。すごい簡単ですね。
doSomethingの処理のうち、何か二次的な処理を裏でこっそりさせたい場合などに有効かと思います。
ちなみに、コード内の”1.seconds”部分は、
“2.minutes”(2分後)、”3.hours(3時間後)”など色々指定が可能です。
定期的なバッチ処理
30分おきに実行させたい処理、日次で実行させたい処理などを想定します。
ここでは、akka-quartz-schedulerを使います。
これは、予め設定しておいたスケジュールに基いてActorを起動する事ができるプラグインです。
事前準備
build.sbtに依存を追加する
build.sbt
1 2 3 4 5 6 7 8 9 |
resolvers ++= Seq( "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" ) libraryDependencies ++= Seq( … … "com.typesafe.akka" %% "akka-quartz-scheduler" % "1.2.0-akka-2.2.x", ) |
※Play2.2環境下の設定です。適宜変更してください。
実装サンプル
1.application.confにスケジュールを設定する
application.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
akka { quartz { defaultTimezone = "Asia/Tokyo" schedules { SampleActor1 { # ← Actorクラス名を指定 description = "15秒おきに実行" # ← 説明を指定 指定するとActorに渡すメッセージとして扱う expression = "0,15,30,45 * * ? * *" # ← スケジュールをcron形式で指定 } SampleActor2 { description = "30分おきに実行" expression = "0 */30 * ? * *" } ・ ・ # ← 追加可能 ・ } } } |
※詳しい設定方法はここをどうぞ。
2.スケジュールを登録する
GlobalSettingsを継承したGlobal.scalaを作成して、1.で設定したファイルを読み込んでスケジュールを登録する処理を書きます。
Global.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import akka.actor.Props import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension import play.api.libs.concurrent.Akka import play.api.libs.concurrent.Execution.Implicits._ import play.api.{ Application, GlobalSettings } trait Global extends GlobalSettings { override def onStart(app: Application) { // Akka Actorのスケジューリング val scheduler = QuartzSchedulerExtension(Akka.system) scheduler.schedules.foreach { case (key, setting) => scheduler.schedule( setting.name, // Actor名 Akka.system.actorOf(Props(Class.forName(s"actors.${setting.name}"))), // ActorRef setting.description.getOrElse(setting.name) // Actorに送信するメッセージ ) } } } |
設定ファイルから情報を読み込んで、動的にActorを生成してスケジューリングしています。
(※ここでは”SampleActor1″、”SampleActor2″クラスが生成されます)
3.Actorのベースを書く
ActorBase.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import akka.actor.Actor import play.api.Logger trait ActorBase extends Actor { /** Akkaから自動的にCallされる */ def receive = { case message: String => { Logger.info(message) // application.confで設定した"15秒おきに実行"という文字列が渡ってくる execute } } /** 処理本体 */ def execute() } |
4.メイン処理のActorを書く
SampleActor1.scala
1 2 3 4 5 6 7 8 9 10 11 12 |
import play.api.Logger class SampleActor1 extends ActorBase { /** 処理本体 */ def execute() = { Logger.info("hogege") … … … } } |
この時のクラス名はapplication.confに設定したActorクラス名と一致させる必要があります。
以上の実装でスケジューリングした通りに勝手にActorが起動、実行されます。
割りと簡単に書けますよね。
※SampleActor2は割愛します。
明日は、最近さらに乗りに乗っているGit大佐こと大佐の番です!!!
元Javaプログラマ。現在はScala/PlayでWeb開発と、SwiftでiOSアプリ開発をしています。 Unitテストとか書いてる時が一番楽しかったりします。