JavaでのAkkaアクターの紹介

JavaでのAkkaアクターの紹介

1. 前書き

Akka is an open-source library that helps to easily develop concurrent and distributed applicationsは、アクターモデルを活用してJavaまたはScalaを使用します。

このチュートリアルでは、we’ll present the basic features like defining actors, how they communicate and how we can kill them。 最後のメモでは、Akkaを使用する際のいくつかのベストプラクティスについても説明します。

2. アクターモデル

アクターモデルは、コンピュータサイエンスコミュニティにとって新しいものではありません。 1973年、Carl Eddie Hewittによって、並行計算を処理するための理論モデルとして初めて導入されました。

ソフトウェア業界が並行アプリケーションと分散アプリケーションを実装することの落とし穴を認識し始めたとき、その実用的な適用性を示し始めました。

An actor represents an independent computation unit.いくつかの重要な特性は次のとおりです。

  • アクターは、その状態とアプリケーションロジックの一部をカプセル化します

  • アクターは非同期メッセージを介してのみ対話し、直接メソッド呼び出しを介しては対話しません

  • 各アクターには一意のアドレスと、他のアクターがメッセージを配信できるメールボックスがあります

  • アクターはメールボックス内のすべてのメッセージを順番に処理します(メールボックスのデフォルト実装はFIFOキューです)

  • アクターシステムはツリーのような階層で構成されています

  • アクターは他のアクターを作成したり、他のアクターにメッセージを送信して自分自身を停止したり、アクターが作成されたりすることができます

2.1. 利点

同期、ロック、共有メモリを処理する必要があるため、同時アプリケーションの開発は困難です。 Akkaアクターを使用することにより、ロックや同期を必要とせずに非同期コードを簡単に作成できます。

メソッド呼び出しの代わりにメッセージを使用する利点の1つは、the sender thread won’t block to wait for a return value when it sends a message to another actorです。 受信側のアクターは、送信者に返信メッセージを送信することで結果を返します。

メッセージを使用するもう1つの大きな利点は、マルチスレッド環境での同期について心配する必要がないことです。 これは、all the messages are processed sequentiallyであるためです。

Akkaアクターモデルのもう1つの利点は、エラー処理です。 アクターを階層化することにより、各アクターは親に障害を通知できるため、それに応じて行動できます。 親アクターは、子アクターを停止または再起動することを決定できます。

3. セットアップ

Akkaアクターを利用するには、Maven Centralから次の依存関係を追加する必要があります。


    com.typesafe.akka
    akka-actor_2.12
    2.5.11

4. アクターを作成する

前述のように、アクターは階層システムで定義されます。 共通の構成を共有するすべてのアクターは、ActorSystem.によって定義されます。

ここでは、デフォルトの構成とカスタム名を使用してActorSystemを定義するだけです。

ActorSystem system = ActorSystem.create("test-system");

アクターはまだ作成していませんが、システムにはすでに3つのメインアクターが含まれています。

  • 名前が状態としてアクターシステム階層のルートを表すアドレス「/」を持つルートガーディアンアクター

  • アドレス「/ user」を持つユーザー保護者アクター。 これは、定義するすべてのアクターの親になります

  • アドレス「/ system」を持つシステム保護者。 これは、Akkaシステムによって内部的に定義されたすべてのアクターの親になります

すべてのAkkaアクターは、AbstractActor抽象クラスを拡張し、他のアクターからの着信メッセージを処理するためのcreateReceive()メソッドを実装します。

public class MyActor extends AbstractActor {
    public Receive createReceive() {
        return receiveBuilder().build();
    }
}

This is the most basic actor we can create.他のアクターからメッセージを受信でき、一致するメッセージパターンがReceiveBuilder.で定義されていないため、それらを破棄します。メッセージパターンマッチングについては、この記事の後半で説明します。

最初のアクターを作成したので、それをActorSystemに含める必要があります。

ActorRef readingActorRef
  = system.actorOf(Props.create(MyActor.class), "my-actor");

4.1. アクター構成

The Props class contains the actor configuration.ディスパッチャー、メールボックス、デプロイメント構成などを構成できます。 このクラスは不変であるため、スレッドセーフなので、新しいアクターを作成するときに共有できます。

Propsオブジェクトの作成を処理するアクターオブジェクト内にファクトリメソッドを定義することを強くお勧めし、ベストプラクティスと見なされています。

例として、テキスト処理を行うアクターを定義しましょう。 アクターは、処理を実行するStringオブジェクトを受け取ります。

public class ReadingActor extends AbstractActor {
    private String text;

    public static Props props(String text) {
        return Props.create(ReadingActor.class, text);
    }
    // ...
}

ここで、このタイプのアクターのインスタンスを作成するには、props()ファクトリメソッドを使用して、String引数をコンストラクターに渡します。

ActorRef readingActorRef = system.actorOf(
  ReadingActor.props(TEXT), "readingActor");

アクターを定義する方法がわかったので、アクターシステム内でどのように通信するかを見てみましょう。

5. アクターメッセージング

相互にやり取りするために、アクターはシステム内の他のアクターとメッセージを送受信できます。 これらのmessages can be any type of object with the condition that it’s immutable

It’s a best practice to define the messages inside the actor class.これは、アクターが処理できるメッセージを理解し、理解しやすいコードを作成するのに役立ちます。

5.1. メッセージを送信する

Akkaアクターの内部では、システムメッセージはメソッドを使用して送信されます。

  • tell()

  • ask()

  • forward()

When we want to send a message and don’t expect a response, we can use the tell() method.これは、パフォーマンスの観点から最も効率的な方法です。

readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());

最初のパラメーターは、アクターアドレスreadingActorRefに送信するメッセージを表します。

2番目のパラメーターは、送信者を指定します。 これは、メッセージを受信するアクターが送信者以外のアクター(たとえば、送信アクターの親)に応答を送信する必要がある場合に役立ちます。

通常、2番目のパラメータはnullまたはActorRef.noSender()に設定できます。これは、応答がないためです。 When we need a response back from an actor, we can use the ask() method:

CompletableFuture future = ask(wordCounterActorRef,
  new WordCounterActor.CountWords(line), 1000).toCompletableFuture();


アクターからの応答を要求すると、CompletionStageオブジェクトが返されるため、処理は非ブロッキングのままです。

私たちが注意しなければならない非常に重要な事実は、応答するアクター内部のエラー処理です。 To return a Future object that will contain the exception we must send a Status.Failure message to the sender actor.

メッセージの処理中にアクターが例外をスローした場合、これは自動的には実行されず、ask()呼び出しはタイムアウトになり、例外への参照はログに表示されません。

@Override
public Receive createReceive() {
    return receiveBuilder()
      .match(CountWords.class, r -> {
          try {
              int numberOfWords = countWordsFromLine(r.line);
              getSender().tell(numberOfWords, getSelf());
          } catch (Exception ex) {
              getSender().tell(
               new akka.actor.Status.Failure(ex), getSelf());
               throw ex;
          }
    }).build();
}

tell()と同様のforward()メソッドもあります。 違いは、メッセージを送信するときにメッセージの元の送信者が保持されるため、メッセージを転送するアクターは中間アクターとしてのみ機能することです。

printerActorRef.forward(
  new PrinterActor.PrintFinalResult(totalNumberOfWords), getContext());

5.2. メッセージを受信する

Each actor will implement the createReceive() method。すべての着信メッセージを処理します。 receiveBuilder()はswitchステートメントのように機能し、受信したメッセージを定義されたメッセージのタイプと一致させようとします。

public Receive createReceive() {
    return receiveBuilder().matchEquals("printit", p -> {
        System.out.println("The address of this actor is: " + getSelf());
    }).build();
}

When received, a message is put into a FIFO queue, so the messages are handled sequentially

6. 俳優を殺す

ActorRefFactoryインターフェースからアクターwe can stop it by calling the stop() methodの使用を終了すると、次のようになります。

system.stop(myActorRef);

このメソッドを使用して、子アクターまたはアクター自体を終了できます。 停止は非同期で行われ、アクターが終了する前のcurrent message processing will finishに注意することが重要です。 No more incoming messages will be accepted in the actor mailbox

stopping a parent actorによって、それによって生成されたwe’ll also send a kill signal to all of the child actors

アクターシステムが不要になったら、アクターシステムを終了してすべてのリソースを解放し、メモリリークを防ぐことができます。

Future terminateResponse = system.terminate();

これにより、システムのガーディアンアクターが停止するため、このAkkaシステムで定義されているすべてのアクターが停止します。

殺したいアクターへのWe could also send a PoisonPill message

myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());

PoisonPillメッセージは、他のメッセージと同様にアクターによって受信され、キューに入れられます。 The actor will process all the messages until it gets to the PoisonPill one。 その場合にのみ、アクターは終了プロセスを開始します。

アクターを殺すために使用される別の特別なメッセージは、Killメッセージです。 PoisonPill,とは異なり、アクターはこのメッセージを処理するときにActorKilledExceptionをスローします。

myActorRef.tell(Kill.getInstance(), ActorRef.noSender());

7. 結論

この記事では、Akkaフレームワークの基本を紹介しました。 アクターを定義する方法、アクターが互いに通信する方法、アクターを終了する方法を示しました。

Akkaを使用する際のいくつかのベストプラクティスで締めくくります。

  • パフォーマンスが懸念される場合は、ask()ではなくtell()を使用してください

  • ask()を使用する場合、常にFailureメッセージを送信して例外を処理する必要があります

  • アクターは可変状態を共有しないでください

  • アクターは別のアクター内で宣言されるべきではありません

  • 参照されなくなったときのactors aren’t stopped automatically。 メモリリークを防ぐために、アクターが不要になった場合は、明示的にアクターを破棄する必要があります

  • アクターが使用するメッセージshould always be immutable

いつものように、記事のソースコードはover on GitHubで入手できます。