Akka Frameworkチュートリアルその2

前回Akka Frameworkのチュートリアルをやってみましたが、動作確認のみで内容を読んでなかったので、軽く読解してみました。チュートリアルというより、Akkaのコンセプトがはっきりわかる内容です。

Akka Project
Tutorial: write a scalable, fault-tolerant, persistent network chat server and client
の「Sample application」以降を読んでみます。サンプルの動作確認方法は、前回のエントリで書きました。

正確に翻訳できるほどの英語力はないので、コードやScalaの冗長な説明は省いて、重要と思われる点だけ、だいたいこんなことが書いてあるんじゃないか程度。間違ってたらごめんなさい。

Creating messages

システム間でやりとりするメッセージが不変(immutable)であることはとても重要。
Actorモデルは、「Actor同士で異なる状態を持たない」という単純なルール上になりたっている。そのためにはつまり、可変のメッセージを送らないことが唯一の方法。

Scalaでは case class という素晴らしいメッセージ作成方法がある。

Client: Sending messages

chat ! message

などとActorにメッセージを送る。! は、応答を待たずに非同期でメッセージを送る。

応答を待つ場合は、!! を使う。
普通の関数のように即時に応答が返るわけではないが、Future(se.scalablesolutions.akka.dispatch.Future)を使って応答を待つ。Futureは別スレッドで動作し、Actorからの応答を待機する。内部ではタイムアウトやリトライなどの管理を行っている。

!!はOption型を返す(説明省略)。

RemoteClientはリモートにあるActorの参照を簡単に取得する。あたかもローカルにあるActorを扱うかのように、透過的にリモートActorを扱うことができる。

Session: Receiving messages

 storage ! message

 storage forward message

メッセージを送る点では両者は同じだが、forwardのほうは「メッセージの送り主(Client)の参照」をstorageに渡すことができる。これによってstorage側では、Clientに直接返信することができるようになる(RedisChatStorageのソースを見るとわかりやすいかも)。

Let it crash: Implementing fault-tolerance

Akkaでは、障害に対して "let it crash(クラッシュさせちゃいなよ)"というアプローチをとっている。これはJavaなどの並列処理に向かない言語・フレームワークが取ってる手法と違う。
letitcrash.comというサイトが・・)

まず、並列処理を考える。
Actorがlinkしていない場合(Actorのlinkについては「NetPenguinの日記」)は、Exception発生時にそもそもハンドリングできない(ログのスタックトレースを見るくらいしかない)。
Linked ActorならException通知やハンドリングができるようになる。Linked Actorを使っていれば、障害発生時、全部復活するか全部殺すか、どちらかの対処ができる

Akkaは、非防衛的(?)プログラミングを奨励している。
障害は起こってしまうものだから、障害発生を妨げるようなことはしないで、その代わり、発生しうる障害を予期したうえで(後述の監視対象Actorのライフサイクルのことを言っていると思われる)、さっさとクラッシュさせて、別の何か(インスタンス・プロセスなど?)に対処させること。

つぎに分散Actorに焦点をあてる。
Actor supervision/linking は、リモートActorの状態監視、リモートActorがダウンしたときにどうするか(同じノードや別のノードでリスタートさせるなど)、などを実現するクリティカルな機能だ。

Supervisor hierarchies

Supervisorとは、子Actorを開始・停止・監視するActorのこと。

Supervisorの基本的な考え方は、「必要に応じて(障害が起きたら)子Actorを再起動」して生きた状態をキープすること。これは耐障害サーバをどのように構築するか、意見の相違があるところだ。

エラーを避けるためのあらゆる手段をつくすのではなく、このアプローチでは「障害を抱擁(許容?)する」という考え方。再起動することで安定した状態にする、これが"let it crash"。

Akkaには2つの再起動方法がある。

OneForOne: クラッシュしたコンポーネントのみ再起動.
AllForOne: 監視している全てのコンポーネントを再起動

AllForOneは、1つクラッシュしたら他のコンポーネントに影響がある場合に使う。

Chat server: Supervision, Traits and more

Supervisor Actorの定義方法には2つ、declaratively(宣言的)とdynamically(動的)あるが、このサンプルではdynamicallyを使う。

まずやることは2つ

1.faultHandlerを使って、障害発生時のハンドラを定義
  する(OneForOneかAllForOneを選択する)
2.再起動のトリガーとなるExceptionを選択して、
  trapExitにセットする。

子Actor(このサンプルではstorage Actor)を監視する最後の手続きは、"link(actor)"を実行すること。
これにより、LinkしたActorがクラッシュした際に、Exceptionを受け、trapExitにマッチした場合に、faultHandlerに応じてActorの再起動が走る。

Session management

Chat message management

このサンプルのようにメッセージブローカーとしてActorを使うのはとても一般的なパターンだ。

STM and Transactors

Actorは独立したプロセス間でメッセージをやりとりするには素晴らしいが、atomicに状態を共有するのは難しい。

この問題を補完するのが、Software Transactional Memory(STM)で、AkkaにもSTMの実装が含まれている。

AkkaはActorとSTMを結合したTransactors(Transactional Actorsの略)という仕組みを提供している。これはベストなActorモデルなんじゃなかろうか。

Akkaでは現在、Map/Vector/Refの3つのトランザクションインターフェースを用意している。これらのオブジェクトは複数のActorで共有できてSTMによって管理されている。そしてトランザクション境界の外でこれらを更新しようとすればExceptionが発生する。

複数のActorが並列的に読み書きできる共有メモリを使うことができる。

トランザクション間で障害が発生したら、中止(ロールバック)するかリトライする。

STMによって、「ACID」のうち、Atomicity/Consistency/Isolationは得られたが、Durability(耐久性)が足りない。しかし、Akkaのpersistence(永続化)moduleが、これを補助してくれる。

Persistence: Storing the chat log

AkkaはSTMインターフェースを拡張する形でPersistence moduleを提供している。バックエンドストレージには、Cassandra、MongoDB、Redisのモジュールが用意されている。

Chat storage: Backed by Redis

atomic { ... } を使ってトランザクションを実行する。
Redisのデータ構造はバイナリなので、今回は文字列をバイナリ変換(message.getBytes("UTF-8"))しているが、もっと複雑なデータでは、インスタンスをシリアライズして格納する。シリアライズの方法はserialization sectionを参照。

ChatServerはCharStorageを監視(supervise)していたわけだが、ここで「監視対象Actor」の側面をみてみよう。

まず、監視対象Actorはライフサイクルを定義する必要がある。RedisChatStorageはPermanentとしてlifeCycleに設定している。

 Permanent: 常に再起動される
 Temporary: 再起動はされないが、
       シャットダウンフックは呼ばれる。

障害時にクラッシュして、安定状態でリスタートして処理を続行するという考え方だが、"安定状態"の定義というのはドメイン依存であり、開発者が定義することだ。

Akkaでは、2つのActor再起動時コールバック関数 preRestart / postRestart を用意しているので、ここで安定状態にセットアップしてやる。エラー原因がわかるようにThrowableを引数に持っている。


(以下割愛)


同じカテゴリのエントリ
1.Lift再入門 / 8.javascriptからsubmitできない / 7.Ajax Form / 6.Radio、Checkboxについて / 5.行列型の編集FORM / 4.サーバーサイドバリデーションとサーバサイド関数 / 3.ログインFORM - S.param使ったら負け / 2.Snippetメソッドとして許される型 / sbt0.12.xで依存jar抽出タスク / scala2.10+lift2.5+NetBeans7.2 / Scalaで入門関数プログラミング / reactive-webを試してみました / Lift2.2M1のテンプレート機能 / Scala Compiler Plugin / View Bound/Context Bound / ScalaZa01参加してきました / Akka Frameworkチュートリアルの次 / Akka Frameworkチュートリアルその2 / Akka Frameworkチュートリアル / LiftでJCaptcha / Url Rewrite Filter / sbt-android-plugin / Android SDK for Scala / 祝Lift2.0リリース / Liftの携帯対応まとめ / Scala2.8への移行 / Lift 2.0-scala280-SNAPSHOT/sbt0.7.1 / Scalaお絵かき環境 - Kojo / Lift+Quartzでバッチ / Scala&Liftを採用した理由 / Liftでdate_select系ヘルパーを作る / LiftでAjax / LiftのSubmitかしこい / lift-mapperのpaginateを使う / snippetをspecする / Lift Mapperを拡張する / LiftのDBをMySQLに / Liftプロジェクト環境を整える / Scala本読み比べてみました / NetBeans6.7&scala / じつはScalaはライトな言語 / Scalaバザ~ル / lift1.0所感 / specsを読む / implicit def / ScalaならNetBeansがサイコー / scala勉強会@東北がスタート / それでも俺はLiftをやるってのか / Scala&Liftセットアップ / ブログリニューアル /
コメント

コメントしてください

closed.