Upgrade to Pro — share decks privately, control downloads, hide ads and more …

リアクティブアーキテクチャ基礎講座

 リアクティブアーキテクチャ基礎講座

かとじゅん

July 16, 2020
Tweet

More Decks by かとじゅん

Other Decks in Programming

Transcript

  1. ⾮同期・ノンブロッキングなメッセージパッシング 受信側はアクティブ時のみリソースを消費できるのでシステムのオーバヘッドを抑制 できる Actor Actor ①メッセージを送信 ②Dispathcer がプッシュする ③タスクを実⾏する Mailbox

    メソッド駆動の場合は呼出先が完了するまで呼出元は解放されず、他のタスクを実⾏ できない。メッセージパッシングの場合はコマンドを送信したらすぐ返答を待たずに 別のタスクを実⾏(Fire And Forget)し、呼出先から返答がきたときに続きを⾏うた め、スケーラビリティが向上する Actor Actor ①コマンドを送信 ③コマンドを送信 ②タスクを実⾏ ②リプライを待た ずに次のタスクへ ④タスクを実⾏ Method Method ①メソッドをコール ③メソッドコールのリターン ②タスクを実⾏ ④次のタスクへ 14
  2. 位置透過性と透過的リモーティング 位置透過性は、アクターの位置情報がローカルであってもリモートのように⾒せるこ と。メッセージ送信側は、送信先のすべてがリモートに⾒える 透過的リモーティングは、リモートからの呼出をローカルからの呼出に⾒せること。 メッセージ受信側は、送信元のすべてがローカルに⾒える マルチスレッディングとRPCを、メッセージ駆動という⼀つのプログラミングモデル で扱えるようになる // ┝戚jパ もハかZぇおてwそおjノテ

    ガげぬdくヅェを わぶ想情wは groupChatRef1 ! AddMessage("Hello World!") object HelloWorld { final case class Greet(whom: String, replyTo: ActorRef[Greeted]) final case class Greeted(whom: String, from: ActorRef[Greet]) // パ もハiね肩し輯sばぃぬdく訣yは def apply(): Behavior[Greet] = Behaviors.receive { (context, message) => context.log.info("Hello {}!", message.whom) message.replyTo ! Greeted(message.whom, context.self) Behaviors.same } } 15
  3. 結果整合性のサブセット 因果⼀貫性 / Causal Consistency 因果関係のある項⽬が、共通した順序で処理されること 逐次⼀貫性 / Sequencial Consistency

    因果関係に関係なく、すべての項⽬が共通した順序で処理されること 並列処理できないのでスケーラビリティが犠牲になる これらの⼀貫性にはそれぞれトレードオフがあることを認識して選択する。 46
  4. Akka Cluster Sharding とは 軽量プロセスであるActorを分散システム上でシャーディングするためのモジュール メッセージはShardRegion → Shard → Entityのようにルーティングされる

    ShadはEntity(集約)のグループ。Entityのスーパーバイザ。Entityを作成する ShardRegionは各ノードで開始される。ShardCoorinatorにShardの場所を要求す る。Shardを⼦アクターとして作成する ShardCoordinatorは調整役。どのShardRegionがどのShardを所有するかを決定 し、ShardRegionに指⽰する。リバランス時はメッセージが移動先のShardRegion にバッファリングされ移動後に移動先のShardに配信される 55
  5. 実装例 Counter Entity(Aggregate) ShardRegion object Counter { sealed trait Command

    case object Increment extends Command final case class GetValue(replyTo: ActorRef[Int]) extends Command def apply(entityId: String): Behavior[Command] = { def updated(value: Int): Behavior[Command] = { Behaviors.receiveMessage[Command] { case Increment => updated(value + 1) case GetValue(replyTo) => replyTo ! value Behaviors.same } } updated(0) } } val TypeKey = EntityTypeKey[Counter.Command]("Counter") // ShardRegionげ粛巌押 val shardRegion: ActorRef[ShardingEnvelope[Counter.Command]] = sharding.init(Entity(TypeKey)(createBehavior = entityContext => Counter(entityContext.entityId))) // ShardRegionくヅェを わぶ想は shardRegion ! ShardingEnvelope("counter-1", Counter.Increment) 56
  6. ネットワーク分断時に⼀貫性か可⽤性を選ぶか CAP定理はそのまま解釈するとミスリードの原因に。完全に排他的選択ではなくバラ ンスが必要。ネットワーク分断時に、⼀貫性を選ぶのか、可能性を選ぶのかという選 択になる シャーディングは⼀貫性を重視しているので、ネットワーク分断が発⽣した場合は可 ⽤性が犠牲になるが、SPoFがないので全体障害にはならない。 Sprit Brain Resolver(Akka2.6.6から標準搭載)はノードが失われたことを検知し て短時間に復旧させることができる。

    ネットワーク分断のときに可⽤性を選択したい場合はCRDT(Conflict-free Replicated Data Type)を使うことができる。実装としてはAkka Distributed Data ⼀貫性を取るか可⽤性を取るかの選択は、技術的な判断ではなくビジネス上の判断で ある。⼀貫性か可⽤性化は0か1かではなく、システムの部分部分で求められるビジ ネス要件に応じて選択する。ビジネスにとって、⼀貫性が保証されないことのインパ クトのほうが⼤きいのか、利⽤できない時間が発⽣することのほうが問題なのかを、 ビジネス側と議論するべき 59
  7. Command and Query Responsibility Segregation コマンド・クエリ責務分離(分離という より隔離に近いイメージ) 2010年 Greg Young⽒が考案したパタ

    ーン。 1997年にBertrand Meyer⽒が考案したコ マンドクエリ分離原則(Command-Query Separation:CQS)をアーキテクチャに適⽤ したものがCQRS。 「あらゆるメソッドは、アクションを実 ⾏するコマンドか、呼び出し元にデータ を返すクエリかのいずれかであって、両 ⽅を⾏ってはならない。これは、質問を することで回答を変化させてはならない ということだ。」 CQRSとは 61
  8. なぜCQRSか そもそもコマンドとクエリの要件に⾮対称性がある Command Query ⼀貫性 結果整合性よりトランザクショ ン整合性を扱うこと多い ほとんどの場合結果整合を使う データ形式 トランザクション処理を⾏い正

    規化されたデータを保存するこ とが好まれる(集約単位など) ⾮正規化したデータ形式を取得 することが好まれる(クライント 都合のレスポンスなど) スケーラビリ ティ 全体のリクエスト⽐率とごく少 数のトランザクション処理しか しない。必ずしもスケーラビリ ティは重要ではない 全体のかなりのリクエスト⽐率 を占める処理を⾏うため、クエ リ側はスケーラビリティが重要 検索・レポートやトランザクション処理を両⽴する単⼀モデルの実現は困難 62
  9. 永続化アクター(Akka Persistence) 副作⽤を起こすコマンド 副作⽤を起こさないコマンド ①コマンドハンドラ ドメインイベントが永続化される ジャーナルDB エンティティ( 集約) アクター

    ②ドメインイベントの永続化 ③イベントハンドラ スナップショットの永続化 ドメインイベントN 件ごとに スナップショットを永続化 アクターがリプレ イ時にSnapshot 以降のイベントが 読み込まれる アクターがリプレイ時に最新の Snapshot があれば読み込まれる 副作⽤が起きると きだけDB にロック なしでイベントが 追記される スナップショットDB 副作⽤を起こすコマンドが受理されるとドメインイベントへの追記保存とメモリ上の 状態遷移が起きる。副作⽤が起きないコマンドはメモリ状態からリプライを返す。コ マンドを処理するために、DBからの読み込みは⼀切不要(パフォーマンスに有利) 不要になった永続化アクターはいつでも破棄できる。再び必要ならば永続化されてい るドメインイベントを基にリプレイすることができる。リプレイするドメインイベン トが⼤量な場合はスナップショットと組み合わせることでリプレイ時間を短縮するこ とができる。 65
  10. 実装例 persistenceIdは集約IDを指定する Stateには、ドメインオブジェクトを格納する コマンドの処理はコマンドハンドラでイベントの処理はイベントハンドラで⾏う sealed trait Command final case class

    Add(data: String) extends Command case object Clear extends Command sealed trait Event final case class Added(data: String) extends Event case object Cleared extends Event final case class State(history: List[String] = Nil) def apply(id: String): Behavior[Command] = EventSourcedBehavior[Command, Event, State]( persistenceId = PersistenceId.ofUniqueId(id), // 綬耗ID emptyState = State(Nil), // 綬耗げ訟尊 commandHandler, // りヂプキコプキネ eventHandler) // ほソプガコプキネ private val commandHandler: (State, Command) => Effect[Event, State] = { (state, command) => command match { case Add(data) => Effect.persist(Added(data)) case Clear => Effect.persist(Cleared) } } private val eventHandler: (State, Event) => State = { (state, event) => event match { case Added(data) => state.copy((data :: state.history).take(5)) case Cleared => State(Nil) } } 66
  11. CQRS & Event Sourcing の Pros/Cons コマンドとクエリに分割することで、コンポーネントが増え、構築や運⽤にかかるコ ストが増⼤するが、それぞれの⽬的に合わせて最適化できる コマンドのドメインモデルとクエリのリードモデルに分割することで、⼀つ⼀つはモ デルがシンプルになる

    コマンド側とクエリ側は別々にスケールさせることができる データベースは増えるが、個々の要件に応じたデータベースを選択できるようになる クエリ要件に基づいたデータ設計を後付けでできる Writeモデルはシャーディングと組み合わせて⼀貫性を保証するが、ネットワーク分 断時に可⽤性を犠牲にしなければならないことがある(ただし、SPoFを回避してリカ バリ可能) 結果整合性のReadモデルは可⽤性を確保しやすい Event Sourcing 監査ログの要件に対応できる ある時点の状態に戻すことができる。問題の調査に使える 不正な状態を修正できる。git revertのように イベントの追記しか発⽣しないため、データベースの処理効率が良い 67
  12. FYI: CQRS & ES with Akka Cluster on AWS akka-cluster

    ReadModel Updater Event Router DynamoDB Streams DynamoDB Read DB ing pod pod pod svc svc pod pod pod pod pod pod pod pod pod Kafka transfer domain events Read API Write API 可⽤性重視 ⼀貫性重視 機能的には問題なし。パフォーマンスとスケーラビリティは知⾒が溜まり次第共有予定 68