初めに
Scalaを普段書くことが多い筆者ですが
Akka、Actor、ウッ分からない状態でしたので
勇気を持ってAkkaに入門してみました。
Hello World記事を書いてみます。
Akkaとは
Lightbend社によって開発された並行プログラミングを実現するオープンソースプロジェクト。
JVMには並行プログラミングをサポートするライブラリに以下のようなものがある。
- java.lang.Thread
- java.util.concurrent
- Future, Promise
Akkaは上記のライブラリと異なり
メッセージドリブンなアクターモデルを採用しているのが特徴。
これによって、並行プログラミングを
シンプルな単一のモデルとして実装することが可能らしい。
また、AkkaはReactive Manifestを提唱をしているみたい。
- Responsive => レスポンスが早いこと
- Resilient => 障害時でも応答性があること
- Elastic => 負荷の変化に対応してリソースを増減できること
- Message Driven => メッセージ駆動であること(疎結合や透明性の保証などメリット多数)
アクターモデルとは
Akkaの章でも少し触れたが、並行分散アプリケーションを作るための概念。
Cal Herwittが1973年に論文として発表し、Erlangによって認知された背景がある。
https://en.wikipedia.org/wiki/Actor_model
アクターの特徴として、他の並列プログラミンを実装するライブラリとは異なり
共有メモリを用いるのではなくメッセージドリブンなところがポイント。
これによってロックが少ない状態で実装することが可能。
概念的にはメッセージキューと良く似ていて
非同期でメッセージパッシングが実行されるため、呼び出し側は処理を待つ必要がなく
アクターはメッセージを送られない限りは何もしない。
シンプルなアクターのクラスを定義してみる
書いてみないとイメージが付かないのでアクタークラスを定義してみよう。
AkkaはActorという基本traitを提供しているため
このtraitを拡張することでアクターモデルを実装することが可能みたい。なるほど。
abstract methodとしてreceiveメソッドが提供されているため
初めはこのメソッドを実装すれば良い。
class MyActor extends Actor {
def receive: PartialFunction[Any, Unit] = {
case hello: String if hello == "hello" => println(hello + " world!!")
case _ => println("received unknown message")
}
}
object Test extends App {
val system = ActorSystem("helloWorld")
val actor = system.actorOf(Props[MyActor])
actor ! "hello"
actor ! "hoge"
}
出力結果は以下。
hello world!!
received unknown message
ふむふむ。なるほどー。
確かにアクターに対してメッセージの送信を行い
アクター自身は内部でハンドリングすることが出来ている。
呼び出し側はアクターの状態や実装を知る必要が無さそうでシンプル。
PropsやらActorSystemやら知らない概念が出ているため噛み砕いて見てみよう。
Propsとは
Propsはアタクー生成時にオプションを指定するための設定クラス。
アクターを生成するためのレシピみたいなもの。
生成時に引数などを渡したりすることが出来る。
val props1 = Props[MyActor]
val props2 = Props(new ActorWithArgs("arg"))
val props3 = Props(classOf[ActorWithArgs], "arg")
また、公式ではファクトリーメソッドを設けることを推奨しているため
今回のケースだと以下のように実装するのが好ましいみたい。
コンパニオンオブジェクトの中でアクターが受け取ることの出来るメッセージを
宣言するのが一般的みたいなので実際に書いてみる。
class MyActor(inputString: String) extends Actor {
import MyActor._
def receive: PartialFunction[Any, Unit] = {
case hello: String if hello == inputString => println(hello + " world!!")
case Hello => println("hello world!!")
case Greeting(greeting) => println(s"hello $greeting")
case _ => println("unknown message")
}
}
// コンパニオンオブジェクト内にファクトリーメソッドとReceiveで受け取る型を定義
object MyActor {
case class Greeting(from: String)
case object Hello
def props(inputString: String): Props = Props(new MyActor(inputString))
}
object Test extends App {
val system = ActorSystem("helloWorld")
val actor = system.actorOf(MyActor.props("hello"))
actor ! "hello"
actor ! Greeting("おはようございます")
actor ! Hello
}
コンパニオンオブジェクトを使用することでかなりシンプルに記述することが出来ている。
ActorSystemとは
アクターシステムはアプリケーションごとに
1つだけ作成されるトップレベルのアクター。
トップレベルのアクターとは何ぞや?と疑問になりましたが
アクターは親子構造になっており
トップレベルのアクターが子供のアクターを持つようなツリー状の構造になっているみたい。
子供のアクターを生成する場合はcontextを使用する。
class TopActor extends Actor {
def receive = ???
}
class ChildActor extends Actor {
val child = context.actorOf(Props[TopActor], name = "myChild")
def receive = ???
}
アクター同士はsender()メソッドを使ってメッセージの送受信を行うことが可能。
class TopActor(childActor: ActorRef) extends Actor {
import TopActor._
def receive: Receive = {
case Start => childActor ! "start"
case Return(msg) => println("return msg:" + msg)
}
}
class ChildActor() extends Actor {
def receive: Receive = { case _ =>
sender() ! Return("hogehoge")
}
}
object TopActor {
case object Start
case class Return(msg: String)
def props(childActor: ActorRef): Props = Props(new TopActor(childActor))
}
object Test extends App {
val system = ActorSystem("helloWorld")
val childActor = system.actorOf(Props[ChildActor])
val topActor = system.actorOf(TopActor.props(childActor))
topActor ! Start
}
まとめ
この記事ではAkkaが提供するActorについてまとめてみました。
雰囲気は掴みつつ、どれだけ嬉しさがあるのかがしっくりきていないのが現状です。
これから積極的に活用してみて、慣れていきたいなと思っています。
次はAkka Stream周りを触ってみようかな。
参考
Akka実践バイブル
https://akka-ja-2411-translated.netlify.app/scala/actors.html#actors-scala
https://qiita.com/f81@github/items/55b83d7f4104b1a4dfac
https://kimutansk.hatenablog.com/entry/20140725/1406238670