Remote Actorを使いプロセスやネットワークを超えて、Actorを統合する方法をサンプルを示しながらご紹介します。
こちらのサンプルの動作する完全版はGitHubを御覧ください。suin/akka-sample-remote-scala
Step 1: プロジェクトを作成する
まず、build.sbtを作成します。こちらのサンプルはわけあって古いバージョンのakkaとScalaを使っていますが、最新のバージョンを指定していただいてもいいかと思います。
build.sbt
name := """akka-sample-remote-scala"""
version := "2.3.10"
scalaVersion := "2.10.4"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.3.3",
"com.typesafe.akka" %% "akka-remote" % "2.3.3"
)
Step 2: 共有するオブジェクトを定義する
リモートとローカルで送受信するオブジェクトを定義します。
common/package.scala
package suin
package object common {
case object Hello
}
Step 3: リモートのActor Systemを実装する
リモートのActor Systemをコーディングします。
remote/RemoteApp.scala
package suin.remote
import java.io.File
import akka.actor.{ActorLogging, Props, ActorSystem, Actor}
import com.typesafe.config.ConfigFactory
import suin.common.Hello
object RemoteApp extends App {
val configFile = getClass.getClassLoader.getResource("remote_application.conf").getFile
val config = ConfigFactory.parseFile(new File(configFile))
val system = ActorSystem("remote-system", config)
val remote = system.actorOf(Props[RemoteActor], name="remote")
system.log.info("Remote is ready")
}
class RemoteActor extends Actor with ActorLogging {
override def receive: Receive = {
case Hello =>
log.info("Remote received {} from {}", Hello, sender)
sender ! Hello
case msg: String =>
log.info("Remote received {} from {}", msg, sender)
sender ! "hi"
case any =>
log.info("Remote received unknown message {} from {}", any, sender)
}
}
次に、リモートの設定ファイルを作成します。
remote-application.conf
akka {
loglevel = "INFO"
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 5150
}
log-sent-messages = on
log-received-messages = on
}
}
Step 4: ローカルのActor Systemを実装する
ローカルのActor Systemをコーディングします。
local/LocalApp.scala
package suin.local
import java.io.File
import akka.actor.{ActorLogging, Props, ActorSystem, Actor}
import com.typesafe.config.ConfigFactory
import suin.common.Hello
class LocalActor extends Actor with ActorLogging {
@throws[Exception](classOf[Exception])
override def preStart(): Unit = {
val remoteActor = context.actorSelection(LocalApp.config.getString("app.remote-system.remote-actor"))
log.info("Remote actor is {}", remoteActor)
remoteActor ! "hi"
remoteActor ! Hello
}
override def receive: Receive = {
case msg: String =>
log.info("Client received {} from {}", msg, sender)
case Hello =>
log.info("Client received {} from {}", Hello, sender)
case any =>
log.info("Client received unknown message {} from {}", any, sender)
}
}
object LocalApp extends App {
val configFile = getClass.getClassLoader.getResource("local_application.conf").getFile
val config = ConfigFactory.parseFile(new File(configFile))
val system = ActorSystem("client-system", config)
val localActor = system.actorOf(Props[LocalActor], name = "local")
}
ローカルの設定ファイルを作成します。
local_application.conf
akka {
loglevel = "INFO"
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 0 # any free port
}
log-sent-messages = on
log-received-messages = on
}
}
app.remote-system.remote-actor = "akka.tcp://remote-system@127.0.0.1:5150/user/remote"
Step 5: ビルドして実行する
ターミナルを2つ立ち上げます。
ひとつのターミナルでリモートのActor Systemを起動します。
% ./activator run
[info] Loading project definition from /Users/suin/Desktop/akka-sample-remote-scala/project
[info] Set current project to akka-sample-remote-scala (in build file:/Users/suin/Desktop/akka-sample-remote-scala/)
Multiple main classes detected, select one to run:
[1] suin.local.LocalApp
[2] suin.remote.RemoteApp
Enter number: 2
もうひとつのターミナルでローカルのActor Systemを起動します。
% ./activator run
[info] Loading project definition from /Users/suin/Desktop/akka-sample-remote-scala/project
[info] Set current project to akka-sample-remote-scala (in build file:/Users/suin/Desktop/akka-sample-remote-scala/)
Multiple main classes detected, select one to run:
[1] suin.local.LocalApp
[2] suin.remote.RemoteApp
Enter number: 1
実行結果がそれぞれコンソールに表示されます。
リモート
[info] Running suin.remote.RemoteApp
[INFO] [06/21/2015 17:02:15.641] [run-main-0] [Remoting] Starting remoting
[INFO] [06/21/2015 17:02:15.799] [run-main-0] [Remoting] Remoting started; listening on addresses :[akka.tcp://remote-system@127.0.0.1:5150]
[INFO] [06/21/2015 17:02:15.801] [run-main-0] [Remoting] Remoting now listens on addresses: [akka.tcp://remote-system@127.0.0.1:5150]
[INFO] [06/21/2015 17:02:15.814] [run-main-0] [ActorSystem(remote-system)] Remote is ready
[INFO] [06/21/2015 17:03:21.080] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://remote-system@127.0.0.1:5150/user/remote] Remote received hi from Actor[akka.tcp://client-system@127.0.0.1:59552/user/local#167765235]
[INFO] [06/21/2015 17:03:21.081] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://remote-system@127.0.0.1:5150/user/remote] Remote received Hello from Actor[akka.tcp://client-system@127.0.0.1:59552/user/local#167765235]
ローカル
[info] Running suin.local.LocalApp
[INFO] [06/21/2015 17:03:20.763] [run-main-0] [Remoting] Starting remoting
[INFO] [06/21/2015 17:03:20.898] [run-main-0] [Remoting] Remoting started; listening on addresses :[akka.tcp://client-system@127.0.0.1:59552]
[INFO] [06/21/2015 17:03:20.900] [run-main-0] [Remoting] Remoting now listens on addresses: [akka.tcp://client-system@127.0.0.1:59552]
[INFO] [06/21/2015 17:03:20.911] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Remote actor is ActorSelection[Anchor(akka.tcp://remote-system@127.0.0.1:5150/), Path(/user/remote)]
[INFO] [06/21/2015 17:03:21.092] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Client received hi from Actor[akka.tcp://remote-system@127.0.0.1:5150/user/remote#-68561685]
[INFO] [06/21/2015 17:03:21.093] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://client-system@127.0.0.1:59552/user/local] Client received Hello from Actor[akka.tcp://remote-system@127.0.0.1:5150/user/remote#-68561685]