JavaでのAkkaアクターの紹介
1前書き
-
Akka
は、アクターモデルを利用することで、JavaまたはScalaを使用して並行分散アプリケーションを簡単に開発するのに役立つオープンソースのライブラリです。
このチュートリアルでは、アクターの定義、コミュニケーション方法、そしてアクターの殺害方法など、基本的な機能について説明します。最後のメモでは、Akkaを使用する際のベストプラクティスについても説明します。
2アクターモデル
アクターモデルは、コンピュータサイエンスコミュニティにとって新しいものではありません。これは、1973年にCarl Eddie Hewittによって最初に導入され、並行計算を処理するための理論モデルとなりました。
ソフトウェア業界が並行アプリケーションと分散アプリケーションの実装の落とし穴を認識し始めたとき、それはその実際的な適用性を示し始めました。
-
アクターは独立した計算単位を表します。
-
アクターはその状態とアプリケーションロジックの一部をカプセル化します。
-
アクターは非同期メッセージを通じてのみ対話し、決して対話することはありません
直接メソッド呼び出し
** 各アクターはユニークなアドレスと他のアクターがあるメールボックスを持っています
メッセージを配信できる
** アクターはメールボックス内のすべてのメッセージを順番に処理します
順序(メールボックスのデフォルト実装はFIFOキューです)
** アクターシステムはツリーのような階層構造になっています
-
アクターは他のアクターを作成することができます、他のアクターにメッセージを送ることができます
自分自身を停止するか、任意のアクターが作成された
2.1. メリット
同期、ロック、共有メモリを扱う必要があるため、並行アプリケーションの開発は困難です。 Akkaアクターを使用することで、ロックや同期を必要とせずに非同期コードを簡単に書くことができます。
メソッド呼び出しの代わりにメッセージを使用する利点の1つは、** 送信側スレッドが別のアクターにメッセージを送信するときに戻り値を待つことをブロックしないことです。受信側のアクターは、返信メッセージを送信側に送信することによって結果を返します。
メッセージを使用するもう1つの大きな利点は、マルチスレッド環境での同期について心配する必要がないことです。これは、** すべてのメッセージが順番に処理されるためです。
Akkaアクターモデルのもう1つの利点はエラー処理です。アクターを階層構造にすることで、各アクターはその親に障害を通知することができるため、それに応じて行動することができます。親アクターは、子アクターを停止するか再開するかを決定できます。
3セットアップ
Akkaアクターを利用するためには、https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22akka-actor__2.12%22[Maven Central]から以下の依存関係を追加する必要があります。
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor__2.12</artifactId>
<version>2.5.11</version>
</dependency>
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();
}
}
-
これは私たちが作成できる最も基本的なアクターです** 他のアクターからメッセージを受け取ることができ、__ReceiveBuilderでは一致するメッセージパターンが定義されていないのでそれらを破棄します。
最初のアクターを作成したので、それを
ActorSystem
に含めます。
ActorRef readingActorRef
= system.actorOf(Props.create(MyActor.class), "my-actor");
4.1. アクター設定
-
Props
クラスにはアクター設定が含まれています** ディスパッチャ、メールボックス、またはデプロイメント設定などを設定できます。
このクラスは不変で、スレッドセーフなので、新しいアクターを作成するときに共有できます。
Props
オブジェクトの作成を処理するファクタメソッドをアクターオブジェクト内に定義することを強くお勧めします。
例証するために、テキスト処理をするアクターを定義しましょう。
アクターは
String
オブジェクトを受け取り、そこで処理を行います。
public class ReadingActor extends AbstractActor {
private String text;
public static Props props(String text) {
return Props.create(ReadingActor.class, text);
}
//...
}
では、このタイプのアクターのインスタンスを作成するために、
props()
factoryメソッドを使用して
String
引数をコンストラクターに渡すだけです。
ActorRef readingActorRef = system.actorOf(
ReadingActor.props(TEXT), "readingActor");
アクターを定義する方法がわかったので、次に、アクターシステム内でそれらがどのように通信するかを見てみましょう。
5アクターメッセージング
互いに対話するために、アクターはシステム内の他のアクターからメッセージを送受信することができます。これらのメッセージは、不変であるという条件で、あらゆる種類のオブジェクトにすることができます。
-
アクタークラス内でメッセージを定義するのがベストプラクティスです。** これは、理解しやすく、アクターが処理できるメッセージを把握しやすいコードを書くのに役立ちます。
5.1. メッセージを送信する
Akkaアクターシステムの内部では、メッセージはメソッドを使用して送信されます。
-
tell()
-
ask()
-
forward()
-
メッセージを送信したいが応答を期待したくない場合は、__tell()メソッドを使用することができます** これはパフォーマンスの観点から最も効率的な方法です。
readingActorRef.tell(new ReadingActor.ReadLines(), ActorRef.noSender());
最初のパラメータは、アクターアドレス
readingActorRef
に送信するメッセージを表します。
2番目のパラメータは送信者が誰であるかを指定します。これは、メッセージを受信したアクターが送信者以外のアクター(送信側アクターの親など)に応答を送信する必要がある場合に役立ちます。
通常、2番目のパラメータは
null
または
ActorRef.noSender()
に設定できます。これは返信を期待しないためです。アクターからの応答が必要なときは、
ask()
メソッドを使用できます。
CompletableFuture<Object> future = ask(wordCounterActorRef,
new WordCounterActor.CountWords(line), 1000).toCompletableFuture();
アクターからの応答を要求すると、
CompletionStage
オブジェクトが返されるため、処理はブロックされないままになります。
私たちが注意を払わなければならないという非常に重要な事実は反応するアクターの中のエラー処理です。 ** 例外を含む
Future
オブジェクトを返すには、送信者アクターに
Status.Failure
メッセージを送信する必要があります。
メッセージの処理中にアクターが例外をスローし、__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. メッセージを受信する
-
各アクターは、すべての受信メッセージを処理する
createReceive()
メソッドを実装します。
receiveBuilder()
はswitch文のように機能し、受信したメッセージを定義されているメッセージのタイプと照合しようとします。
public Receive createReceive() {
return receiveBuilder().matchEquals("printit", p -> {
System.out.println("The address of this actor is: " + getSelf());
}).build();
}
-
メッセージは受信されるとFIFOキューに入れられるため、メッセージは順番に処理されます。
6. 俳優を殺す
アクターの使用が終わったら、
ActorRefFactory
インターフェースから
stop()
メソッド** を呼び出してそれを停止できます。
system.stop(myActorRef);
このメソッドを使用して、任意の子アクターまたはそのアクター自体を終了させることができます。
停止は非同期で行われ、アクターが終了する前に
現在のメッセージ処理が
終了することに注意することが重要です。
-
これ以上アクターメールボックスに入ってくるメッセージは受け付けられません** 。
-
親アクターを停止する
ことによって、
それによって生み出されたすべての子アクター** にもkillシグナルを送ります。
アクターシステムが不要になったら、それを終了してすべてのリソースを解放し、メモリリークを防ぐことができます。
Future<Terminated> terminateResponse = system.terminate();
これにより、システムの保護者アクター、つまりこのAkkaシステムで定義されているすべてのアクターが停止します。
-
私たちは殺したいどんなアクターにも
PoisonPill
message ** を送ることができます。
myActorRef.tell(PoisonPill.getInstance(), ActorRef.noSender());
PoisonPill
メッセージは、他のメッセージと同様にアクターによって受信され、キューに入れられます。
アクターは
PoisonPill
one
に到達するまですべてのメッセージを処理します。その後アクターは終了プロセスを開始します。
俳優を殺すために使われるもう一つの特別なメッセージは
Kill
メッセージです。
PoisonPillとは異なり、このメッセージを処理するときに、アクターは
ActorKilledException__をスローします。
myActorRef.tell(Kill.getInstance(), ActorRef.noSender());
7. 結論
この記事では、Akkaフレームワークの基本を紹介しました。アクターを定義する方法、それらが互いに通信する方法、そしてそれらを終了する方法を示しました。
Akkaを使用する際のベストプラクティスをまとめます。
-
パフォーマンスが問題になる場合は
ask()
の代わりに
tell()
を使用 -
ask()
を使うときは、常に例外を送ることで例外を処理するべきです。
失敗メッセージ
** アクターは不定状態を共有してはいけません
-
俳優は他の俳優の中で宣言されるべきではありません
-
俳優が自動的に停止することはありません
参照されました。必要のないときは明示的にアクターを破壊しなければなりません
もうメモリリークを防ぐために
アクターによって使用されるメッセージは常に不変でなければなりません
いつものように、この記事のソースコードはhttps://github.com/eugenp/tutorials/tree/master/libraries[over GitHub]から入手できます。