SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Akkaで学ぶアクターモデル入門

非同期処理と対障害性を実現する、アクターモデルを用いたアプリケーション構成

Akkaで学ぶアクターモデル入門 第3回

Akkaアプリケーションの作成

 ここからはソースコードを交えてAkkaによるアプリケーションの作り方を紹介します。

 この記事で紹介しているソースコードは、記述量を抑えるため重要な部分のみ抜き出して書いており、完全に動く形にはなっていません。

 Akka公式ドキュメントの“How to get started”からQuickstart Guildeのリンクをたどると、JavaソースだけでなくMavenやGradleの設定ファイルを含む実行可能なサンプルが取得できます。そのままダウンロードしたサンプルを実行できますし、この記事の内容に従ってJavaソースコード部分を書き換えて動かしてみるのもよいでしょう。

 Akkaのアプリケーションを作るときはまずActorSystemオブジェクトが必要で、多くの場合Main関数の中で1つだけ作成します。下記のコードのようにActorSystem.create()メソッドを使ってActorSystemオブジェクトを作成します。

import akka.actor.typed.ActorSystem;

public class Main {
  public static void main(String[] args) {
    final ActorSystem guardian =
      ActorSystem.create(MyGuardianActor.create(), "mysystem")
  }
}

 ActorSystemの作成時は同時にガーディアンと呼ばれるアクターを作成します。上記のコードでは、ガーディアン・アクターが受け取るメッセージの型で、MyGuardianActor.create()によってガーディアン・アクターを作成しています。

 後ほど詳しく説明しますが、Akkaのアクターは全てツリー状の親子関係の中に存在し、ガーディアン・アクターはその親子関係の頂点となる親です。Akkaでアクターを実装するときに使うActorContextはspawnメソッドを持っていて、以下のように親アクターから子アクターを生成できます。

//contextはActorContext型
context.spawn(MyChildBehavior.create(), "mychild");

 親アクターから子アクターを生成し、多数のアクター同士がメッセージを送信し合って協調するのがAkkaのアプリケーションです。

メッセージ送受信

 アクターに対してメッセージを送るときは、直接アクターのメソッドを外部から呼び出すことはできないので、間接オブジェクトであるActorRefのtellメソッドを使います。そうするとメッセージは受信側ActorRefのメッセージキューに追加されます。

//actorRefはActorRef型
actorRef.tell(new MessageXyz(... /*引数*/));
//tellメソッドのシグネチャ
void tell​(T msg); //Tはジェネリックの型パラメタ

 シグネチャの通り、アクターはtellメソッドを呼ぶ際にメッセージの送信先であるActorRefを必要とします。そこで、送信先であるActorRefの供給方法として代表的なものを以下に記します。

 まずはアクターが受け取るメッセージの中にActorRefが含まれている場合。アクターでの処理が終わった後、送信元に別のメッセージを投げ返す必要があるときにこの方法を使います。下図のリクエスト・レスポンス パターンや、フロントエンド・バックエンド パターンなどが代表的です。

 自身の子アクターにメッセージを送るときは、ActorContextのspawnメソッドを呼び出した際、MapなどにActorRefをあらかじめ保存しておくとよいでしょう。保存せずに、ActorContextのgetChildやgetChildrenメソッドを代わりに使う方法がありますが、こちらは推奨されていません。戻り値の型がActorRef<Void>となり型安全なActorRefでなくなるため、子アクターにメッセージを送る用途での利用には適していません。

String childName = "mychild";

//contextはActorContext型
ActorRef childRef =
  context.spawn(MyChildBehavior.create(), childName);

mapInsideActor.put(childName, childRef);

 送信元や子供に当たるアクターではなく、全く別のアクターにメッセージを送る場合はアクターの生成時にコンストラクタのパラメタとしてActorRefを渡す方法と、Actor Discovery/Receptionistを通してActorRefを渡す方法の2つが利用できます。Discoveryは連載記事の第7回で紹介予定のAkkaのクラスタリング機能、つまり分散コンピューティング環境でも使用できる汎用性の高いものです。

 Akka公式ドキュメントでは、アクターのインタラクションパターンとして、アクター同士がどのような経路でメッセージをやり取りするかの例が載っています。アプリケーションを構成する際の参考にするとよいでしょう。

状態遷移図を使ったモデリング

 ここまではAkkaのアクターがメッセージをやり取りする仕組みを紹介しました。ここからはメッセージを受け取ったアクターが内部でどのように処理を行うのかを説明します。以下の図はアクターがメッセージを受信して処理を行うたび内部状態を変化させる様子を状態遷移図で表したものです。

 状態遷移図と聞くと難しいと感じる人がいるかもしれません。しかし高度な理論は必要なく、コードを書くときに図を描きながら思考を整理すると、効果的にアクターを使える、と考えてください。実際に次回の記事で段階を追ってアクターを実装する手順をお話します。

耐障害性

 Akkaアプリケーションの作成の説明で触れたように、Akkaのアクターは全てツリー状の親子関係の中に存在します。その理由は耐障害性の実現です。そもそもAkkaはErlangのアクター機構に着想を得て作成された、JavaとScalaから利用できるライブラリです。Erlangは電話会社のシステムを支えるなど、常に安定した動作が要求される分野で長く利用された実績があります。そのErlangアクターの耐障害性実現の仕組みはAkkaでも取り入れられていて、アクターの親子関係はその要となります。

 アクターの親子関係の頂点はガーディアン・アクターですが、実はガーディアン・アクターには3種類あります。以下のAkka公式ドキュメントから引用した画像をごらんください。

 1つはユーザ・ガーディアンで、ActorSystem.createメソッドに渡すアクターです。もう1つはシステム・ガーディアンで、こちらはAkkaアクターを使ったプログラムの開発者は意識しません。Akkaのアクターには普段開発者から見えなくても、データベースとの接続に使われるアクターや、クラスタリング構成で別マシン上のアクターと通信するときに媒介となるアクターなど、さまざまなアクターが裏で動いています。システム・ガーディアンはその名の通り、これらのシステム側で走らせているアクターの親子関係の頂点です。最後に、ルート・ガーディアンはユーザ・ガーディアンとシステム・ガーディアン両方の親です。

 アクターが投げる例外はその親によって補足され、親は例外を投げた子アクターを停止するか、リスタートするか、あるいは他の動作を行うか選択できます。子アクターの例外に対する親アクターの取り扱いを決めたルールはSupervisorStrategyというオブジェクトで管理します。

SupervisorStrategy strategy = new OneForOneStrategy(
        10,
        Duration.ofMinutes(1),
        DeciderBuilder
            .match(ArithmeticException.class, e -> SupervisorStrategy.resume())
            .match(NullPointerException.class, e -> SupervisorStrategy.restart())
            .match(IllegalArgumentException.class, e -> SupervisorStrategy.stop())
            .matchAny(o -> SupervisorStrategy.escalate())
            .build());

 Akkaの耐障害性については注意すべき点があります。親アクターが子アクターの例外を補足して管理できるからと言って、やみくもに子アクターが例外を投げるのはよくありません。前回の記事で説明したように、アクターは1つのメッセージを受け取った後、処理の開始から完了まで割り込みなしで実行されるため、非同期処理を安全に実行できます。しかし、処理の途中で、内部状態が中途半端に変更されたまま例外を投げてしまうと、処理を再開したときに割り込みが発生したのと同じ状態になります。

 アクターが例外を投げるのは、本当に予定外の挙動をしたときのみに限定し、かつ例外が投げられても内部状態の変更が中途半端にならないようにしましょう。例外を投げる可能性のある処理が全て終わった後、最後に内部状態を変更するとよいでしょう。あるいは例外発生時、アクターのリスタート前にPreRestart/PostStopシグナルを使って内部状態をきれいにするなどの方法を活用できます。

まとめ

 この記事ではいったん伝統的な3層アーキテクチャであるWeb層・アプリケーション層・データベース層という構成から離れて、アクター同士がメッセージを送受信して処理を実現するという方法でのアプリケーションの作り方を紹介しました。多くの人が使い慣れた3層アーキテクチャではなく、アクターによるアプリケーション構成を用いるのはなぜでしょう?

 その理由の1つは非同期処理です。3層アーキテクチャでは非同期処理における排他制御をデータベースのロックとトランザクション機構に頼ることが多いかと思います。しかし、それではアプリケーション層の非同期処理を書いているときに、データベース層を意識することになります。いわゆる密結合の状態です。Akkaのアクターはデータベースのロックやトランザクションを意識することなく、アクター自体で安全に非同期処理を実行できます。こうしてデータベース層から疎結合になったアクターは、いわゆるビジネスロジックをきれいに保つ助けにもなるでしょう。

 もう1つの理由はアクターの対障害性です。近年のシステムは大規模化・分散化する例が多くなっています。アクターは親子関係の構造を持つことで、ミクロなレベルでも対障害性を持っています。また、第7回の記事で紹介するように、クラスタリング機構によってマクロなレベル、つまりクラスタを構成する一部のホストが停止した場合でも分散システムとしての対障害性を持ちます。

 今回はアクターモデルを用いたアプリケーション構成を紹介しました。次回の記事では、いよいよアクターを使ったモデリング、実装の紹介をします。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Akkaで学ぶアクターモデル入門連載記事一覧

もっと読む

この記事の著者

リチャード 伊真岡(リチャード イマオカ)

 大学卒業後、9年間証券会社にてプログラマ兼技術サポートとして勤務。その後いくつかの企業でバックエンド・エンジニアや技術広報などを務める。  プライベートでは過去にScala/JavaのOSSであるakkaへ貢献。 Twitter:@RichardImaokaJP

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/12247 2020/05/21 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー