$30 off During Our Annual Pro Sale. View Details »

Akka Typed - 型安全にメッセージパッシングする! / Akka Typed - typesafe messaging

hayasshi
November 10, 2018

Akka Typed - 型安全にメッセージパッシングする! / Akka Typed - typesafe messaging

hayasshi

November 10, 2018
Tweet

More Decks by hayasshi

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

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

    View Slide

  24. 注意
    これからのサンプルコードは資料の都合上、好ましくない書き方
    をしているコードがあります
    特に 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

    View Slide

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

    View Slide

  26. アクター生成と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

    View Slide

  27. アクター生成と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

    View Slide

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

    View Slide

  29. アクター生成と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

    View Slide

  30. アクター生成とAsk - Akka Typed
    アクター生成
    ActorContext[T].spawn
    振る舞いのなか(
    コンテキストのあるスコープ)

    生成することができる
    SpawnProtocol
    はアクター生成するための組み込みメッセージ
    対応する振る舞いも標準で用意されている
    SpawnProtocol.behavior
    2018-11-10 Scala
    関西Summit 2018
    30

    View Slide

  31. アクター生成と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

    View Slide

  32. アクター生成と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

    View Slide

  33. アクター生成と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

    View Slide

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

    View Slide

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

    View Slide

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

    障害性を高める
    2018-11-10 Scala
    関西Summit 2018
    36

    View Slide

  37. スーパービジョン - 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

    View Slide

  38. スーパービジョン - 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

    View Slide

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

    Restart
    OneForOneStrategy

    Stop
    2018-11-10 Scala
    関西Summit 2018
    39

    View Slide

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

    View Slide

  41. アクターのライフサイクルとは
    アクターには生成から停止して削除されるまでのライフサイクルが
    ある
    ライフサイクルの各ポイントでフックでき、様々な処理を追加でき

    let it crash
    の思想に従い、問題が起きたときのハンドリングを設計
    し、組み込むことができる
    スーパービジョンと組み合わせることでアプリケーション全体の耐
    障害性を高める
    2018-11-10 Scala
    関西Summit 2018
    41

    View Slide

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

    View Slide

  43. ライフサイクル - 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

    View Slide

  44. ライフサイクル - 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

    View Slide

  45. ライフサイクル - 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

    View Slide

  46. ライフサイクル - 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

    View Slide

  47. ライフサイクル - 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

    View Slide

  48. ライフサイクル - 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

    View Slide

  49. ライフサイクル - 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

    View Slide

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

    View Slide

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

    View Slide

  52. 他のモジュールとの統合
    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

    View Slide

  53. 他のモジュールとの統合
    その他のモジュールとの統合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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide