Slide 1

Slide 1 text

Akka Typed 型安全にメッセージパッシングする! 2018-11-10 Scala 関西Summit 2018 1

Slide 2

Slide 2 text

自己紹介 林 大介 Twitter: @hayasshi_ GitHub: hayasshi ChatWork 株式会社 Scala 2.9 系から使い始め(2013 年頃) 型好き、Akka 好き 2018-11-10 Scala 関西Summit 2018 2

Slide 3

Slide 3 text

お話すること Akka や アクターモデルについて簡単におさらい Akka Typed について 各パターンでの比較 アクター生成とAsk スーパービジョン ライフサイクル 他のモジュールとの統合 2018-11-10 Scala 関西Summit 2018 3

Slide 4

Slide 4 text

お話しない( できない) こと Akka Actors とのパフォーマンス比較 簡易なものでもやりたかった!スミマセン Akka Typed の導入事例 Akka Typed への移行戦略について 2018-11-10 Scala 関西Summit 2018 4

Slide 5

Slide 5 text

Akka と アクターモデル 2018-11-10 Scala 関西Summit 2018 5

Slide 6

Slide 6 text

メニーコア時代における、並行・分散処理をおこなう アプリケーションを構築するためのツールキット 一台のマシン上でのアプリケーションから 複数台のマシンでの分散処理やクラスタリングなどを構築するため のモジュールが用意されている これらの根底にあるものは アクターモデル 2018-11-10 Scala 関西Summit 2018 6

Slide 7

Slide 7 text

アクターモデル 1973 年 カール・ヒューイット氏が発表した 並行処理を行うための計算モデル 全てのものはアクターである 全てのものはオブジェクトであると同様な考え方 違いは本質的に並行性を備えているということ アクターはメッセージに対する振る舞いを持つ アクター間のメッセージのやり取りで計算を行う 2018-11-10 Scala 関西Summit 2018 7

Slide 8

Slide 8 text

アクターモデル 2018-11-10 Scala 関西Summit 2018 8

Slide 9

Slide 9 text

詳しくは 2018-11-10 Scala 関西Summit 2018 9

Slide 10

Slide 10 text

お話する前提のバージョン 下記のバージョンで動作確認等を行っています。 val scalaVersion = "2.12.7" val akkaVersion = "2.5.17" 2018-11-10 Scala 関西Summit 2018 10

Slide 11

Slide 11 text

Akka Actors Akka の全ての基礎となる、根底のモジュール JVM 上でアクタープログラミングを行うことができる JVM で並行処理をおこなうにあたっての大変な部分を隠蔽し、 ロジックに集中できる 2018-11-10 Scala 関西Summit 2018 11

Slide 12

Slide 12 text

Akka Actors akka-scala-seed.g8 class Greeter(message: String, printerActor: ActorRef) extends Actor { import Greeter._ import Printer._ var greeting = "" def receive: Receive = { case WhoToGreet(who) => greeting = message + ", " + who case Greet => printerActor ! Greeting(greeting) } } 2018-11-10 Scala 関西Summit 2018 12

Slide 13

Slide 13 text

Akka Actors val system: ActorSystem = ActorSystem("helloAkka") val printer: ActorRef = system.actorOf(Printer.props, "printerActor") val howdyGreeter: ActorRef = system.actorOf(Greeter.props("Howdy", printer), "howdyGreeter") val goodDayGreeter: ActorRef = system.actorOf(Greeter.props("Good day", printer), "goodDayGreeter") howdyGreeter ! WhoToGreet("Akka") howdyGreeter ! Greet // Howdy, Akka howdyGreeter ! WhoToGreet("Scala") howdyGreeter ! Greet // Howdy, Scala goodDayGreeter ! WhoToGreet("Play") goodDayGreeter ! Greet // Good day, Play 2018-11-10 Scala 関西Summit 2018 13

Slide 14

Slide 14 text

LGTM! しかし気をつけなければいけないことも... 2018-11-10 Scala 関西Summit 2018 14

Slide 15

Slide 15 text

Akka Actors val system: ActorSystem = ActorSystem("helloAkka") val printer: ActorRef = system.actorOf(Printer.props, "printerActor") val howdyGreeter: ActorRef = system.actorOf(Greeter.props("Howdy", printer), "howdyGreeter") val goodDayGreeter: ActorRef = system.actorOf(Greeter.props("Good day", printer), "goodDayGreeter") howdyGreeter ! Greeting("Akka") howdyGreeter ! Greeting("Scala") goodDayGreeter ! Greeting("Play") なんか動きそう。コンパイルも通る。でも動かない... 2018-11-10 Scala 関西Summit 2018 15

Slide 16

Slide 16 text

tell ( ! ) メソッド型安全じゃない問題 trait ScalaActorRef { ref: ActorRef ⇒ def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit } type Receive = PartialFunction[Any, Unit] trait Actor { def receive: Receive } アクター定義時に指定していないメッセージを受け取れてしまう ( コンパイルできてしまう) コードベースが大規模になってくると間違ったメッセージを送って しまう可能性が高くなる 2018-11-10 Scala 関西Summit 2018 16

Slide 17

Slide 17 text

型安全じゃない 2018-11-10 Scala 関西Summit 2018 17

Slide 18

Slide 18 text

Akka Typed 2018-11-10 Scala 関西Summit 2018 18

Slide 19

Slide 19 text

Akka Typed Akka Actors のメッセージパッシングを 型安全に行うためのモジュール Akka Actors の置き換えになる 基本的な部分のAPI はだいたい固まっている 他モジュールとの統合API の部分を中心に細かな変更が続いている 2018-11-10 Scala 関西Summit 2018 19

Slide 20

Slide 20 text

Akka Typed libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion アクターの定義は、振る舞い Behavior を定義する 振る舞いは、次の振る舞いを返す入れ子構造になっている import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.{ActorContext, Behaviors} case class Greet(whom: String) val greeterBehavior: Behavior[Greet] = Behaviors.receive { (ctx: ActorContext[Greet], msg) => ctx.log.info("Hello {}!", msg.whom) Behaviors.same } 2018-11-10 Scala 関西Summit 2018 20

Slide 21

Slide 21 text

Akka Typed アクターは振る舞い Behavior から生成される import akka.actor.typed.ActorSystem val greeter = ActorSystem(greeterBehavior, "greeter") greeter ! Greet("Akka") // Hello Akka! case class Greeting(whom: String) greeter ! Greeting("Akka") // compile error ( ActorSystem もActorRef[T] を継承している) 型安全にメッセージパッシングできる! 2018-11-10 Scala 関西Summit 2018 21

Slide 22

Slide 22 text

Akka Typed - 状態 def counterBehavior(current: Int): Behavior[Int] = Behaviors.receiveMessage { c => val count = current + c println(count) counterBehavior(count) } val counter = ActorSystem(counterBehavior(0), "counter") counter ! 1 // 1 counter ! 3 // 4 状態は再帰をつかって表現する よりイミュータブルな振る舞いとして扱うことができる 2018-11-10 Scala 関西Summit 2018 22

Slide 23

Slide 23 text

なるほど良さそうに見える 型安全にメッセージパッシングできることはわかった しかし、Akka Actors に比べ 大きくコーディングスタイルが変わっている Akka Actors でよく使われる アクタープログラミングのパターンをどう記述していくか比較する 2018-11-10 Scala 関西Summit 2018 23

Slide 24

Slide 24 text

注意 これからのサンプルコードは資料の都合上、好ましくない書き方 をしているコードがあります 特に Akka Actors で Props を利用側が直接使っている箇所は 注意してください Props にインスタンスを利用側から渡すと、利用側でインスタン スの状態を書き換えることができてしまいます // ダメゼッタイ val actor = new Greeter(...) val props = Props(actor) val actorRef = system.actorOf(props, "child") actor.greeting = "hoge" 2018-11-10 Scala 関西Summit 2018 24

Slide 25

Slide 25 text

パターン比較 アクター生成とAsk 2018-11-10 Scala 関西Summit 2018 25

Slide 26

Slide 26 text

アクター生成とAsk - Akka Actors class Producer extends Actor { def receive: Receive = { case p: Props => val child = context.actorOf(p) context.watch(child) sender() ! context.actorOf(p) } } class Printer extends Actor { def receive: Receive = { case s: String => println(s) } } 2018-11-10 Scala 関西Summit 2018 26

Slide 27

Slide 27 text

アクター生成とAsk - Akka Actors val system = ActorSystem() val producer = system.actorOf(Props(classOf[Producer])) import akka.pattern.ask implicit val ec = system.dispatcher implicit val timeout: Timeout = Timeout(3.seconds) val printer: Future[ActorRef] = (producer ? Props(classOf[Printer])).mapTo[ActorRef] for (printerRef <- printer) { printerRef ! "Hello" } 2018-11-10 Scala 関西Summit 2018 27

Slide 28

Slide 28 text

アクター生成とAsk - Akka Actors アクター生成 ActorSystem.actorOf , ActorContext.actorOf アクターの内部どこでも生成することができる Ask import akka.pattern.ask で ask メソッド( ? ) を使えるようになる ask メソッドの返り値は Future[Any] のため mapTo をつかい、型を変換する ( 返ってくる実際の型と異なれば実行時エラー) 28

Slide 29

Slide 29 text

アクター生成とAsk - Akka Typed val printerBehavior: Behavior[String] = Behaviors.receiveMessage { msg => println(msg) Behaviors.same } val producerBehavior: Behavior[SpawnProtocol] = Behaviors.receive { (ctx, msg) => msg match { case Spawn(bhvr, name, props, replyTo) => val ref = ctx.spawn(bhvr, name, props) ctx.watch(ref) replyTo ! ref } Behaviors.same } 2018-11-10 Scala 関西Summit 2018 29

Slide 30

Slide 30 text

アクター生成とAsk - Akka Typed アクター生成 ActorContext[T].spawn 振る舞いのなか( コンテキストのあるスコープ) で 生成することができる SpawnProtocol はアクター生成するための組み込みメッセージ 対応する振る舞いも標準で用意されている SpawnProtocol.behavior 2018-11-10 Scala 関西Summit 2018 30

Slide 31

Slide 31 text

アクター生成とAsk - Akka Typed val producerBehavior: Behavior[SpawnProtocol] = Behaviors.receive { (ctx, msg) => msg match { case Spawn(bhvr, name, props, replyTo) => val ref = ctx.spawn(bhvr, name, props) ctx.watch(ref) replyTo ! ref } Behaviors.same } val spawnBehavior = SpawnProtocol.behavior val producer = ActorSystem(spawnBehavior, "producer") ただし ActorContext[T].watch を差し込めない 2018-11-10 Scala 関西Summit 2018 31

Slide 32

Slide 32 text

アクター生成とAsk - Akka Typed val producer = ActorSystem(producerBehavior, "producer") import akka.actor.typed.scaladsl.AskPattern._ implicit val ec = producer.executionContext implicit val timeout: Timeout = Timeout(3.seconds) implicit val scheduler: Scheduler = producer.scheduler val printer: Future[ActorRef[String]] = producer ? SpawnProtocol.Spawn(printerBehavior, "printer") for (printerRef <- printer) { printerRef ! "Hello" } 2018-11-10 Scala 関西Summit 2018 32

Slide 33

Slide 33 text

アクター生成とAsk - Akka Typed Ask import akka.actor.typed.scaladsl.AskPattern._ で、 ask メソッド( ? ) を使えるようになる ActorRef[T] から実際の型が返る def ?[U](replyTo: ActorRef[U] ⇒T)(implicit...): Future[U] 戻し先となる ActorRef[U] をメッセージに含める case class Request(s: String, replyTo: ActorRef[Response]) server ?(ref => Request("data", ref) server ? (Request("data", _)) 2018-11-10 Scala 関西Summit 2018 33

Slide 34

Slide 34 text

アクター生成とAsk - 比較まとめ アクター生成 大きな違いはない Typed は ActorSystem ( ヒエラルキートップ) からの 子アクター生成がメッセージ経由になる Ask 返ってくるメッセージに型がある! ask メソッドで送るメッセージに戻り先の ActorRef[U] を含める必要がある ( 送り先のActorRef[T] も受け付けれる必要がある) 2018-11-10 Scala 関西Summit 2018 34

Slide 35

Slide 35 text

パターン比較 スーパービジョン 2018-11-10 Scala 関西Summit 2018 35

Slide 36

Slide 36 text

スーパービジョンとは アクターには親子関係があり、アクターヒエラルキーを構成する 子アクターがクラッシュした際に、親アクターが子アクターをどう 扱うかを管理する仕組み 適切なエラーハンドリングを行い、状態を復元したり、再起動させ たり、安全に停止させることで、ヒエラルキー全体の自己復元力, 耐 障害性を高める 2018-11-10 Scala 関西Summit 2018 36

Slide 37

Slide 37 text

スーパービジョン - Akka Actors class Supervisor extends Actor { override val supervisorStrategy: SupervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1.minute) { case _: ArithmeticException ⇒ Resume case _: NullPointerException ⇒ Restart case _: IllegalArgumentException ⇒ Stop case _: Exception ⇒ Escalate } def receive: Receive = { case p: Props ⇒ sender() ! context.actorOf(p) } } class Child extends Actor { def receive: Receive = { case ex: Exception ⇒ throw ex 37

Slide 38

Slide 38 text

スーパービジョン - Akka Typed val childBehavior: Behavior[Exception] = Behaviors.receiveMessage(throw _) val supervisorBehavior: Behavior[Exception] = Behaviors.supervise { Behaviors.supervise { Behaviors.supervise { childBehavior }.onFailure[ArithmeticException](SupervisorStrategy.resume) }.onFailure[NullPointerException]( SupervisorStrategy.restartWithLimit( maxNrOfRetries = 10, withinTimeRange = 1.minute ) ) }.onFailure[IllegalArgumentException](SupervisorStrategy.stop) 2018-11-10 Scala 関西Summit 2018 38

Slide 39

Slide 39 text

スーパービジョン Actors Typed 戦略定義位置 親アクター Behavior への組み込み 戦略種別 OneForOneStrategy AllForOneStrategy OneForOneStrategy 対応内容種別 Resume , Restart , Stop Escalate Resume , Restart , Stop 再起動制御 あり あり デフォルトの戦略 OneForOneStrategy で Restart OneForOneStrategy で Stop 2018-11-10 Scala 関西Summit 2018 39

Slide 40

Slide 40 text

パターン比較 ライフサイクル 2018-11-10 Scala 関西Summit 2018 40

Slide 41

Slide 41 text

アクターのライフサイクルとは アクターには生成から停止して削除されるまでのライフサイクルが ある ライフサイクルの各ポイントでフックでき、様々な処理を追加でき る let it crash の思想に従い、問題が起きたときのハンドリングを設計 し、組み込むことができる スーパービジョンと組み合わせることでアプリケーション全体の耐 障害性を高める 2018-11-10 Scala 関西Summit 2018 41

Slide 42

Slide 42 text

アクターのライフサイクルとは Akka 実践バイブルより引用 2018-11-10 Scala 関西Summit 2018 42

Slide 43

Slide 43 text

ライフサイクル - Akka Actors class IdPrinterActor extends Actor { override def preStart(): Unit = { println(s"preStart ${self.path}") super.preStart() } override def preRestart(reason: Throwable, message: Option[Any]): Unit = { println(s"preRestart $message ${self.path}") super.preRestart(reason, message) } override def postRestart(reason: Throwable): Unit = { println(s"postRestart ${reason.getMessage} ${self.path}") super.postRestart(reason) } override def postStop(): Unit = { println(s"postStop ${self.path}") super.postStop() } 2018-11-10 Scala 関西Summit 2018 43

Slide 44

Slide 44 text

ライフサイクル - Akka Actors class IdPrinterActor extends Actor { // ...lifecycle hook override def receive: Receive = { case Message(id) => id match { case 0L => throw new Exception("Id is zero!") case 9L => context.stop(self) case _ => println(id) } } } 2018-11-10 Scala 関西Summit 2018 44

Slide 45

Slide 45 text

ライフサイクル - Akka Actors object IdPrinterActor { def name(id: Long): String = s"id-printer-$id" def props: Props = Props(classOf[IdPrinterActor]) } val system = ActorSystem() val printer1 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(1L)) printer1 ! Message(1L) val printer0 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(0L)) printer0 ! Message(0L) val printer9 = system.actorOf(IdPrinterActor.props, IdPrinterActor.name(9L)) printer9 ! Message(9L) 2018-11-10 Scala 関西Summit 2018 45

Slide 46

Slide 46 text

ライフサイクル - Akka Actors 見やすいように調整しています preStart akka://default/user/id-printer-1 1 preStart akka://default/user/id-printer-0 [ERROR] [11/08/2018 23:12:35.727] [default-akka.actor.default-dispatcher-4] [akka: java.lang.Exception: Id is zero! at ... preRestart Some(Message(0)) akka://default/user/id-printer-0 postStop akka://default/user/id-printer-0 postRestart Id is zero! akka://default/user/id-printer-0 preStart akka://default/user/id-printer-0 preStart akka://default/user/id-printer-9 postStop akka://default/user/id-printer-9 2018-11-10 Scala 関西Summit 2018 46

Slide 47

Slide 47 text

ライフサイクル - Akka Typed val idPrintBehavior: Behavior[Message] = Behaviors.supervise { Behaviors.setup[Message] { ctx => println(s"start ${ctx.self.path}") Behaviors.receiveMessage[Message] { msg => msg.id match { case 0L => throw new Exception("Id is zero!") case 9L => Behaviors.stopped case _ => println(msg.id);Behaviors.same } }.receiveSignal { case (context, PreRestart) => println(s"preRestart ${context.self.path}");Behaviors.same case (context, PostStop) => println(s"postStop ${context.self.path}");Behaviors.same } } }.onFailure(SupervisorStrategy.restart) 2018-11-10 Scala 関西Summit 2018 47

Slide 48

Slide 48 text

ライフサイクル - Akka Typed def idPrinterName(id: Long): String = s"id-printer-${id.toString}" val system = ActorSystem(SpawnProtocol.behavior, "lifecycle") import akka.actor.typed.scaladsl.AskPattern._ implicit val ec: ExecutionContext = system.executionContext implicit val timeout: Timeout = Timeout(3.seconds) implicit val scheduler: Scheduler = system.scheduler for { printer1 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(1L)) printer0 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(0L)) printer9 <- system ? SpawnProtocol.Spawn(idPrintBehavior, idPrinterName(9L)) } { printer1 ! Message(1L) printer0 ! Message(0L) printer9 ! Message(9L) } 2018-11-10 Scala 関西Summit 2018 48

Slide 49

Slide 49 text

ライフサイクル - Akka Typed 見やすいように調整しています setup akka://lifecycle/user/id-printer-1 1 setup akka://lifecycle/user/id-printer-0 preRestart akka://lifecycle/user/id-printer-0 setup akka://lifecycle/user/id-printer-0 [ERROR] [11/08/2018 23:51:35.515] [lifecycle-akka.actor.default-dispatcher-2] [ java.lang.Exception: Id is zero! at ... setup akka://lifecycle/user/id-printer-9 postStop akka://lifecycle/user/id-printer-9 2018-11-10 Scala 関西Summit 2018 49

Slide 50

Slide 50 text

ライフサイクル Actors Typed フック定義 override して拡張 super 呼び出しが必須 receiveSignal にて Behavior を拡張 フック箇所の種類 preStart , postStop preRestart , postRestart PreRestart , PostStop 再起動時の フック箇所 preRestart postStop postRestart preStart PreRestart 2018-11-10 Scala 関西Summit 2018 50

Slide 51

Slide 51 text

他のモジュールとの統合 2018-11-10 Scala 関西Summit 2018 51

Slide 52

Slide 52 text

他のモジュールとの統合 Akka Streams との統合用API を含むモジュールが提供されている libraryDependencies += "com.typesafe.akka" %% "akka-stream-typed" % akkaVersion ActorSource や ActorSink などのコンポーネントをつかい バッファや OverflowStrategy を設定して Akka Streams の Graph に Akka Actors を組み込むことができる 2018-11-10 Scala 関西Summit 2018 52

Slide 53

Slide 53 text

他のモジュールとの統合 その他のモジュールとの統合API も絶賛開発されていっています ( 主にクラスター関連) "com.typesafe.akka" %% "akka-cluster-typed" % akkaVersion "com.typesafe.akka" %% "akka-cluster-sharding-typed" % akkaVersion "com.typesafe.akka" %% "akka-persistence-typed" % akkaVersion 2018-11-10 Scala 関西Summit 2018 53

Slide 54

Slide 54 text

他のモジュールとの統合 詳しくは公式ドキュメントやリリースノートを参照 https://doc.akka.io/docs/akka/current/typed/index.html https://akka.io/blog/news-archive.html 2018-11-10 Scala 関西Summit 2018 54

Slide 55

Slide 55 text

まとめ 2018-11-10 Scala 関西Summit 2018 55

Slide 56

Slide 56 text

まとめ Akka Typed になり より型安全にコーディングができるようになった コーディングスタイルが大きく変わるので切り替えが必要 細かな部分で Akka Actors と挙動がことなる 他のモジュールとの統合API も充実してきている アクタープログラミングの本質的な部分は変わっていない 2018-11-10 Scala 関西Summit 2018 56

Slide 57

Slide 57 text

参考・引用 Akka Documentation Akka 実践バイブル Wikipedia アクターモデル 2018-11-10 Scala 関西Summit 2018 57