akkaフレームワークを使用したアクターベースの...

7
ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013 JAVA TECH 76 COMMUNITY JAVA IN ACTION ABOUT US blog //polyglot programmer / 2回シリーズのパート1で は、信頼性と同時実行性を 兼ね備えた分散システムの構築の 難しさについて説明し、そのよう な分散システムを構築するための 「アクター」という概念を簡単に 紹介しました。次に、Java仮想マ シン(JVM)でアクターを実装す る方法であるAkkaについて説明 しました。Akkaは、Scala、Play フレームワークと共にTypesafe Stackに含まれています。記事の 中でいくつかの単純な例を示しま したが、紙面の制約上、深く掘り下 げることはしませんでした。 パ ート 1 で は 、アクタ ー の 基 本事項を整理するために、少 し単純化して説明しました。 A k k a のド キュメント で は 、ア クターを「状態(State)、振る 舞い(Behavior)、メールボッ クス(Mailbox)、子(Child)、 および監視戦略(Supervisor Strategy)のコンテナである」と 説明しています。アクターが内部 に状態を保持できるという点で 状態のコンテナであること、また、 コードを含むという点で振る舞い のコンテナであることは、パート 1ですでに確認しました。ただし、 アクターのコードの実行は、メソッ ド呼出しに応答するものではな く、アクターのメールボックスを介 して、アクターに送信されたメッ セージに応答するものです。その ため、同時実行性に関するバグの 主要因となる、複数のアクターが 相互にブロックし合うような状況 が避けられます。 アクターの構成要素のうち、残 り2つの「子」と「監視戦略」は、複 数のアクターが連携して大きなタ スクを実行する場合(ほとんどの 場合に当てはまりま すが)に重要になりま す。Akkaには堅牢な システムを構築するた めのさまざまなアイデ アがありますが、それ をどう取り入れるかと いう点の大部分は監 視戦略が担っていま す。この監視戦略では、 「失敗させる」という 考え方を採用しており、従来型の 多くのJ2EE/Java EEスタックで 採用されている「失敗を完全に防 ぐ」という戦略とは対照的です。 それでは、これらの点について 1歩ずつ確認していきましょう。 監視と命令 パート1では、前半の「Hello」サン プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し 確認しましたが、これらのサンプル にはある小さな欠陥があります。 アクター内部で問題が発生した場 合に、システム全体(大きなもので はありませんが)がク ラッシュし、しかも再起 動しません。 コンソールで実行 するような簡易なシス テムの場合は、システ ムを起動したコマンド をユーザーがいつで も再実行できるため、 問題にはなりません。 しかし、わずかでも信 頼性が求められるシステムでは、 あまり良いことではありません。 たとえば、単純にコンソールに 出力するのではなく、アクターで 何らかの「リスクの高い」振る舞い を扱っているとしましょう。ここで は、例外をスローする可能性のあ る動作で構成された振る舞いを 扱うことにします。このような状況 は、パート1の前半のサンプルに あるHelloActor 内にcase文を 作成し、使用された場合に準ラン ダムに例外をスローすることで再 現できます(リスト1参照)。 リスト1はわざとらしいサンプ ルではありますが、それでも少な くとも一定の回復の余地のある1 つの失敗のシナリオになっていま す。このサンプルの場合は、ただ 繰り返し試行を続けることで、最 終的には回復する、すなわちメッ セージを出力することができま す。 もっとも、 HelloActorの設計は 少しリファクタリングする必要が あります。現時点のHelloActor は単独ですべてを実行できるわ Akkaライブラリを使用して単純なアクターの作成よりも高度な操作を行う方法 TED NEWARD BIO 写真: PHIL SALTONSTALL パート2 Akkaフレームワークを使用したアクターベースの システムの構築 ハング・アップ 待ち時間に制約が課 されない場合はデッ ドロック、すなわち 本番環境が応答しな くなる「ハング」が、 本質的にいつでも発 生し得る状態となり ます。 Ted Neward (@tedneward) Neudesic のアーキ テクチャ・コンサル タント。複数の専門 家グループに貢献。 『Effective Enterprise Java』 (Addison-Wesley Professional、 2004年)、 『Professional F# 2.0』(Wrox、 2010 年) など著書多数。 Java、Scala をは じめとするテクノロ ジー記事の掲載やカ ンファレンスでの講 演も多い。

Upload: others

Post on 12-Jan-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

76

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer /

全2回シリーズのパート1では、信頼性と同時実行性を

兼ね備えた分散システムの構築の難しさについて説明し、そのような分散システムを構築するための「アクター」という概念を簡単に紹介しました。次に、Java仮想マシン(JVM)でアクターを実装する方法であるAkkaについて説明しました。Akkaは、Scala、Playフレームワークと共にTypesafe Stackに含まれています。記事の中でいくつかの単純な例を示しましたが、紙面の制約上、深く掘り下げることはしませんでした。パート1では、アクターの基

本事項を整理するために、少し単純化して説明しました。Akkaのドキュメントでは、アクターを「状態(State)、振る舞い(Behavior)、メールボックス(Mailbox)、子(Child)、および監視戦略(Supervisor Strategy)のコンテナである」と説明しています。アクターが内部に状態を保持できるという点で状態のコンテナであること、また、

コードを含むという点で振る舞いのコンテナであることは、パート1ですでに確認しました。ただし、アクターのコードの実行は、メソッド呼出しに応答するものではなく、アクターのメールボックスを介して、アクターに送信されたメッセージに応答するものです。そのため、同時実行性に関するバグの主要因となる、複数のアクターが相互にブロックし合うような状況が避けられます。アクターの構成要素のうち、残

り2つの「子」と「監視戦略」は、複数のアクターが連携して大きなタスクを実行する場合(ほとんどの場合に当てはまりますが)に重要になります。Akkaには堅牢なシステムを構築するためのさまざまなアイデアがありますが、それをどう取り入れるかという点の大部分は監視戦略が担っています。この監視戦略では、「失敗させる」という

考え方を採用しており、従来型の多くのJ2EE/Java EEスタックで採用されている「失敗を完全に防ぐ」という戦略とは対照的です。 それでは、これらの点について

1歩ずつ確認していきましょう。

監視と命令パート1では、前半の「Hello」サンプルと後半のPiを計算するサンプルを使用して、アクターベースのシステムの設計方法について少し確認しましたが、これらのサンプルにはある小さな欠陥があります。アクター内部で問題が発生した場合に、システム全体(大きなもので

はありませんが)がクラッシュし、しかも再起動しません。コンソールで実行

するような簡易なシステムの場合は、システムを起動したコマンドをユーザーがいつでも再実行できるため、問題にはなりません。しかし、わずかでも信

頼性が求められるシステムでは、あまり良いことではありません。たとえば、単純にコンソールに

出力するのではなく、アクターで何らかの「リスクの高い」振る舞いを扱っているとしましょう。ここでは、例外をスローする可能性のある動作で構成された振る舞いを扱うことにします。このような状況は、パート1の前半のサンプルにあるHelloActor内にcase文を作成し、使用された場合に準ランダムに例外をスローすることで再現できます(リスト1参照)。リスト1はわざとらしいサンプ

ルではありますが、それでも少なくとも一定の回復の余地のある1つの失敗のシナリオになっています。このサンプルの場合は、ただ繰り返し試行を続けることで、最終的には回復する、すなわちメッセージを出力することができます。もっとも、HelloActorの設計は

少しリファクタリングする必要があります。現時点のHelloActorは単独ですべてを実行できるわ

Akkaライブラリを使用して単純なアクターの作成よりも高度な操作を行う方法TED NEWARD

BIO

写真: PHIL SALTONSTALL

パート2

Akkaフレームワークを使用したアクターベースの システムの構築

ハング・アップ待ち時間に制約が課されない場合はデッドロック、すなわち本番環境が応答しなくなる「ハング」が、本質的にいつでも発生し得る状態となります。

Ted Neward (@tedneward) Neudesic のアーキテクチャ・コンサルタント。複数の専門家グループに貢献。 『Effective Enterprise Java』(Addison-Wesley Professional、2004年)、『Professional F# 2.0』(Wrox、2010年) など著書多数。Java、Scalaをはじめとするテクノロジー記事の掲載やカンファレンスでの講演も多い。

Page 2: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

77

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / けではありません。例外がスローされHelloActorの制御が外れれば、アクターが死んでしまうからです。そこで、この「リスクの高い」操作を実行する新しいアクターを作成し、HelloActor内からそのアクターを使用する必要があります(リスト2参照)。

リスト2のコードを実行した場合、面白いことが起こります。幸運にも(このサンプルの場合は幸運とは言えませんが)時刻の末尾がゼロとなった場合、例外がトリガーされますがプログラムは終了しません。例外は子アクター(HelloActor内から作成されたアクター)からスローされるため、元のアクターの監視戦略(デフォルトではOneForOneStrategy)は、子アクター内からスローされる例外を監視します。戦略に即した命令(例外の型をDirective型にマッピングしたもの)に応じて、監視戦略により実行すべき処理が決定されます。その処理の種類は以下のとおりです。■■ 失敗したアクターのメッセージ処理の再開(アクターは壊滅的な失敗をしたわけではなく、そのまま利用できる場合)

■■ 失敗したアクターの再起動(アクターは壊滅的な失敗をしたため、メッセージを処理するための新しいアクターを作成する必要がある場合)

■■ アクターの停止■■ ア ク タ ー の 独 自 の 監 視 役(HelloActorの場合はAkkaシステム・ランタイム)に例外を再スローすることによる、問題のエスカレーションたとえば、RuntimeExceptionはデ

フォルトで、上記の「再起動」のオプショ

ンにマッピングされています。再起動のオプションを指定した場合、Akkaは失敗したアクターを再起動します。アクター内部のパターン・マッチングで例外の型を調べることで、この状況を確認できます(リスト3参照)。

まったく新しい振る舞いが必要な場合は、supervisorStrategyの値をオーバーライドする必要があります。標準的な方法としては、アクターのサブタイプの中でオーバーライドします(リスト4参照)。SupervisorStrategyのコンストラク

タの説明はこれですべてというわけではありませんが、パラメータmaxNrOfR-etriesとwithinTimeRangeについて本記事で説明する必要はないでしょう。名前から機能を判断できますし、Akkaに関するScaladocに詳しい説明があります。また、一般的によく使用されるデフォルト値が設定されています。

コンテキストの内容注目すべき点として、リスト4のコードでは「コンテキスト値」を使用しています。コンテキスト値とは、すべてのアクターに元々バインドされている値であり、アクターが実行されるアクター・システムへの参照を提供するものです。コード・レベルでは、コンテキスト値はActorContextオブジェクトの参照です。ActorContextオブジェクトには、アクターで使用するための興味深い情報や振る舞いを返す、以下のようなさまざまなメソッドが含まれます。 ■■ actorOf: このアクター(actorOfを呼び出すアクター)の子となる別のアクターを作成(リスト4で使用)

■■ children: このアクターの子アクターすべてに対する参照のIterableを返す

■■ parent: このアクターの親■■ self: このアクター自身に対する参照。this参照とは少し異なり、selfは生のオブジェクトへの参照ではなく、アクターをラップするActorRefに対する参照である。ほとんどの場合はthisよりもselfの方が利用に適して

いる■■ stop: このアクターのシャットダウンを実行

■■ dispatcher: このアクターに対して使用されるMessageDispatcherを返す。MessageDispatcherとは、メッセージをアクターに配信する際の戦略であり、おもにアクターとスレッドの関係を表す(例:アクターが個別に

class HelloActor extends Actor { def messageOut(msg: String) = { if ((System.currentTimeMillis() % 10) == 0) throw new RuntimeException("NOOO! Even time! The horror!") else System.out.println(msg) }

def receive = { case Start => messageOut("Hello, Akka") case incoming : RepeatMessage => for (it <- 0 to incoming.num) messageOut("Repeat: " + incoming.message) case incoming : Message => messageOut("Message: " + incoming.message) case Stop => context.system.shutdown() }}

LISTING 1 LISTING 2 LISTING 3 LISTING 4

Download all listings in this issue as text

Page 3: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

78

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / スレッドを保持するか、すべてのアクターを1つのスレッドで実行するか)

■■ system: このアクターが属するActorSystemへの参照

■■ sender: : 現在処理しているメッセージの送信元のアクター単純ではないアクターのほぼすべて

で、リスト4のように、実行中のいずれかの時点でコンテキストを使用します。ActorContextのメソッドはほかにもあり、そのうち2つのメソッドについては後ほど説明しますが、ほとんどのメソッドの詳しい説明はAkkaに関するScaladocに記載されています。

黙っていないで応答せよパート1では、!メソッドを使用して、あるアクターが別のアクターにメッセージを送信できることを示しましたが、双方向のリクエスト/レスポンスをアクターで処理する方法については意図的に説明を省略しました。なぜなら、すべてのメッセージ送信を単方向の非同期通信ととらえて設計したシステムは、全体的に非常に迅速かつスムーズに機能するためです。ブロックが発生しないため、デッドロックが起こり得ません。アクターは自身のsender参照を使用

して常に単方向のメッセージをメッセージの送信元に送り返すことができるということを理解しておくことが重要です。したがって、リスト5a、5bのような処理はどのような場合でも実現できます。しかし、この方法には問題があります。

RobinHoodActorで従来型の!構文を使用してメッセージを送信する場合(リスト6)、出力される情報(リスト7)は、予想とはまったく異なるものになってしま

います。おそらく意図した出力内容ではありません。メッセージは非同期的に処理されるた

め、Robinが子分(My men)に発信したメッセージは、順序を無視して到着したようです。たしかに、Robinに返された結果は、親分が通常期待するような順序(命令を下した直後、次の命令を下す前に応答が得られる状態)ではありません。サンプルでは、リクエスト/レスポンス

形式のメッセージを記述することも可能です。しかし、すでに説明したとおり、ブロックが発生する危険があり、当然ながらより慎重に検討して作業しなければならないため、もっと多くのコーディングが必要になります。ただし、Akkaでは、リクエスト/レスポンス形式で記述する場合であっても、ブロック発生の原因となるような、制約のないリクエスト/レスポンス用のメソッドは用意されていません。Akkaではメッセージは非同期的に送信され、送信元はFutureオブジェクトを受け取ります。このFutureオブジェクトが(任意の時点で)結果を受信します。リクエスト/レスポンス形式のメッセー

ジ送信ではaskメソッド(短縮形「?」)を使用しますが、その前に(おもにシステムの応答待ち時間に関する)いくつかの情報を指定しておく必要があります。ここで、リスト8のようなエコーを行

う単純なアクターがあるとします。このコードは読みづらいのですが、正常に動作します。送信元では、アクターの作成後、?メソッドを使用してメッセージを送信します。このメソッドは、事前に暗黙的なTimeoutオブジェクトを作成している場合のみ動作します(作成していない場合は、コンパイル・エラーが発生します。

ただし、askメソッドを使用する場合は、構文上は長くなりますがタイムアウトのパラメータを明示的に指定できるため、コードがやや読みやすくなります)。メッセージの送信後、?呼出しに

よって返されるオブジェクトは、特定のFuture[結果]型にマッピングされます(本記事のサンプルでは、文字列が返されることを想定しているため、Future[String]型にマッピングする必要があります)。最後に、リスト9のように、Futureオブジェクトに結果が設定される

までの待ち時間を指定する必要があります。結果が設定された後は、プログラムでAkka APIを使用して実際にその結果を抽出できます。なぜこのような面倒な手順になって

いるのでしょうか。Michael Nygard氏は、著書『Release It!』の中で、待ち時間に制約が課されないことが本番品質のコードにとって致命的となる理由を詳細に説明しています。本記事ではその詳細については割愛しますが、要約すると、待ち時間に制約が課されない場合

case class RichMan(val kind : String)case object Sheriffcase class Rob(val whom : String)case class Hide(val where : String)case class JobComplete(val msg : String)

class RobinHoodActor extends Actor { val rightHandMan = context.actorOf(Props[LittleJohnActor]) def receive = { case rm : RichMan => { println( "Robin Hood says, 'Ho, ho! Rob the " + rm.kind + "!'") rightHandMan ! Rob(rm.kind) } case Sheriff => { println("Robin Hood says, 'The Sheriff! Hide, men!") rightHandMan ! Hide("forest") } case j : JobComplete => println("Robin Hood says, 'My men report '" + j.msg + "''") }}

LISTING 5a LISTING 5b LISTING 6 LISTING 7 LISTING 8

Download all listings in this issue as text

Page 4: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

79

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / はデッドロック、すなわち本番環境が応答しなくなる「ハング」が、本質的にいつでも発生し得る状態となります。そのため、タイムアウト値を繰り返し使用して、ハングする可能性のある各操作の厳密な待ち時間を指定します。この待ち時間が経過した場合、回復が行われ、実行が再開されます(これにより、メッセージまたはレスポンスの消失から回復する機会が得られます)。ところで、リスト9のコードをコンパイ

ルするためには、コードの前に特定のインポート文(リスト10)を記述する必要があります。askメソッドも?メソッドもActor APIには存在しないからです。これらのインポート文を使用しない場合、コンパイル時に誤解を生じるようなメッセージが表示されます。言うまでもなく、Akkaは単方向のメッ

セージを使いやすくするものであり、コーディング量は増えますが、正しくコーディングすることで冗長性や耐障害性に一段と優れたシステムの構築が可能になります。

専門分野は専門家に複数のアクター間でメッセージがやり取りされる様子を見て、AkkaはJava Message Service(JMS)やその他のメッセージ指向のミドルウェア・システムに置き換わるものなのだろうかと疑問に思う人もいるかもしれません。表面上はたしかにそのとおりであり、特にメッセージのルーティング(次の項で説明)については一部に類似性が見られます。しかし、類似している点はそれだけであり、表面的です。メッセージ指向のミドルウェア・システムでは一般的に、メッセー

ジやメッセージの配信を、開発者が確認し対処する必要のある最高レベルの関心事として扱います。耐久性、永続性などがそのおもな理由となっています。一方、アクターベースのシステムでは、メッセージは実装の詳細に近く、単にメソッドの呼出しにブロックが発生するという性質を打破するものとして使用します。そして、メッセージは(メールボックス内にある場合)開発者が対処したいものでも、対処する必要があるものでもありません。しかし、面白いことに、この2種類のシ

ステムにはいくつかの類似点があります。その1つは、連携して共通の目標を達成できる複数のアクターの「ワークフロー」あるいは「パイプライン」を作成するという点です。やや懐古的に思われるでしょうが、たとえば、UNIXのgrep、sed、awkといったコマンドライン・ツールを使用するなどして複数の小さな部品から大きなシステムを構築することは、大規模でモノリシックなコードを構築することよりもはるかに回復力が高く堅牢で、長期的な成功につながる方法です。このようなコードのそれぞれは、

しばしば個別のアクターの型として表されます。PrintlnActorや、数学の領域で言えばFibonacciActor、Fac to r i a l Ac t o r、数学以外であればProcessReques tActo r、T r a n s f e r M o n e y A c t o r 、NotifyAccountActorといった具合です。これらのアクターには、それぞれのステップを(たとえばA→B→C→Dのように)直線的に連結して実行すると動作するものもあります。その場合は、単純

に「次の」アクターを前のアクターに渡すことができます。しかし、それ以外の場合は、一部のステップについて、並行処理または連結処理、あるいはその両方の組合せを使用して処理する必要があります。今取り上げている話題はAkkaでは「ルーティング」と呼ばれています。ルーティングのすべての可能性を説明することはできませんが、一部の単純なシナリオについて見ていきましょう。ここで、リスト11のように、コンソー

ルに出力するための1つのアクターを作成します。リスト11では、PrintlnActorの5つの異なるインスタンスを作成します(インスタンス化の際に出力される「created!」というメッセージによって、それぞれのインスタンスを確認できます)。より重要な点は、10個のメッセージを送信した場合に、ラウンドロビン方式でメッセージを各アクターにルーティングすることです(リスト12参照)。このコードを実行した場合、実際に5

つのアクターが作成され、それぞれのアクターがメッセージを2つずつ順番に受け取る様子を確認できます(リスト13

参照)。こ こ で 重 要 に な る の は

roundRob inRoute rの作成部です。roundRobinRouterは5つのPrintlnActorのコレクションを実質的に「指揮」するアクターであり、クライアント(メイン・メソッド)にとっては1つのアクターしかいないように見えます。しかし、出力からも分かるように、実際には5つのインスタンスに対して作業が分割されています。Akkaは以下のような、セマンティクス

の異なるさまざまなルーター(Router)を提供しています。■■ RoundRobinRouter: 前述のとおり、ラウンドロビン方式(リング状)で各アクターにメッセージを渡す

■■ RandomRouter: 名前からわかるように、ランダムに選択したアクターにメッセージを渡し、その他の点については考慮しない

■■ SmallestMailboxRouter: 管理下にあるすべてのアクターのメールボックスを調査し、最小のメールボックス(つまり処理中の仕事が一番少ない

val ma = system.actorOf(Props[MockingActor])implicit val timeout = Timeout(5 seconds)val future = (ma ? "Hello").mapTo[String]val result = Await.result(future, 5 seconds)println("Received result: " + result)

LISTING 9 LISTING 10 LISTING 11

Download all listings in this issue as text

Page 5: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

80

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / メールボックス)にメッセージを渡す。Akkaではリモート・プロセスで実行中のアクターに関するメールボックスの状態は不可視であるため、リモートのアクターはすべて最後に選択される点に注意

■■ BroadcastRouter: 管理下にあるすべてのアクターにブロードキャスト形式でメッセージを渡す

■■ ScatterGatherFirstCompleted Router: BroadcastRouterに機能を追加したルーター。メッセージを各アクターにディスパッチするが、最初に返されたアクターの結果のみを受け入れ、その他の結果は破棄する。メッセージング・システムでよく見られる「Scatter/Gather」パターンの事実上の実装サンプルでは示していませんが、

DynamicResizerを作成し、それをルーター作成時の「resizer」引数として渡すことで、アクター数が変動するルーターを作成することも可能です。また、カスタムのルーターを作成することもできます。たとえば、ScatterGatherFirst Comp le tedRoute rに類似しているものの、最初の結果を取得するのではなく最初の2つの結果を取得して比較し、一致していることを確認するRedundancyRouterを作成することができます。必要な場合は、Akkaのマニュアルに一例が記載されていま

す。

別人になれ言うまでもなく、システム構築の際のよくある実装の1つに、クライアントによるオブジェクトの段階的な使用に合わせて、そのオブジェクトの内部の状態を管理することがあります。Akkaには、アクターの状態を管理するための強力な機能があり、受信するメッセージに応答するコードをアクターで変更できます。この機能はbecomeという当たり障りのない名前のメソッドの裏に隠されています。このメソッドのメカニズムは基本的には単純であり、becomeが呼び出されるたびに、ある別のメソッドが、処理のためにメッセージがアクターに配信されたときに実行されるメソッドになります。リスト14を確認します。

リスト14のコードに、以下のようなメッセージを送信できます(メッセージの正確な型はここでの説明においては重要でないため、パート1のStartメッセージを流用します)。

コンソールに出力される文字列は以下のとおりです。

この結果を見て、有限状態マシン(Finite State machine : FSM)の動

val pp = system.actorOf( Props[PingPongActor])pp ! Startpp ! Startpp ! Start

It begins!PINGPONG

作を明示的にアクター内に組み込むことができるという結論に達したとすれば、まさしくそのとおりです。実際のところ、Akkaではさらに明示的に、アクター内でFSMを表現する方法をサポートしています。FSMモジュールの詳細についてはAkkaのマニュアルで説明されています(becomeの機能は単純な状態遷移の実装には適していますが、非常に複雑な状態マシンの場合は、状態やその遷移についてさらに厳密に定義する必要があります。この機能はFSMモジュールにより提供されます)。

object Main { def main(args: Array[String]): Unit = { val system = ActorSystem()

val roundRobinRouter = system.actorOf(Props[PrintlnActor]. withRouter(RoundRobinRouter(5)), "router") 1 to 10 foreach { i => roundRobinRouter ! i } }}

LISTING 12 LISTING 13 LISTING 14

Download all listings in this issue as text価値ある作業Akkaは単方向のメッセージを使いやすくするものであり、コーディング量は増えますが、正しくコーディングすることで、冗長性や耐障害性に一段と優れたシステムの構築が可能になります。

Page 6: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

81

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / どこか遠くで驚くべきことかもしれませんが、今のところ、まだリモートのアクターについてはまったく説明していません。この理由は簡単です。リモートのアクターに対するプログラマのアプローチは、単純にメッセージを送信するという、ローカルのアクターに対するアプローチと本質的にまったく同じものであるからです。異なる点は、リモート通信のための設定を行い(Akkaでコードの実行時に検索できるカスタムのテキスト形式のファイルに適切なエントリを記述します)、リモート・システム上のアクターの検索(または作成)の方法を指定することだけです。どちらのタスクも、アクターとの相互作用の性質やアクターベースのシステムの設計全体を大きく変えるものではありません。設定の観点から、Akkaではリスト15

に示すエントリを含む、akka.confファイルを用意する必要があります。actorセクションでは、ローカルのアクターの代わりに、ネットワーク経由でメッセージを受信できるアクターを使用することを宣言します。また、remoteセクションでは、利用する通信トランスポートの種類やさまざまなTCP/IPレベルの詳細(ホスト名、ポート番号など)を指定します。本記事のサンプルでは、外部の要件

を最小限に抑えるために、設定をコード内に埋め込み、ConfigFactoryを使用して直接読み取ることにします(なお、ConfigFactoryは技術的にはScalaの一部ではありません。TypesafeのWebサイトにScaladocがありますので、参照してください)。言うまでもなく、この方法を本番用のコードで使用することは

お勧めしません。作成するアクターをリスト16に示します。リスト16のコードも、重要な部分に焦点を絞るために、意図的に単純化しています。次に、リスト17に示すように、リモー

ト・マシン上のサーバーについて設定する必要があります(ここでも単純化のために、実行中の同一マシンを指定します)。ActorSystemに渡す設定内容はともかく、サーバーに関するコードは、ごく標準的なAkkaのコードです。acto-rOfを使用してアクターを作成します。ただし今回は、クライアントからこのアクターを検索できるように、名前を指定しています。次に、プロセスの稼働状態を維持する目的で、(System.inを使用して)キーストロークの受信時にブロックしています。この方法についても、本番用のコードで使用することはお勧めしません。次に、クライアントですが、こちらも驚

くほど単純化しています(リスト18参照)。これまでと違う点は、actorOfの代わりにactorForメソッドを使用し、パラメータとして、リモートのアクターの場所を検索するためのURIを指定していることです。URIの形式についての説明は必要ないでしょう。ほとんどのURI、そしてほとんどの情報は以下のとおりサーバーの設定に含まれています。■■ スキーム(akka)■■ サーバーのActorSystemの名前(server)■■ サーバーのホスト名(生のIPアドレス127.0.0.1)

■■ サーバーのポート番号(2552)■■ アクターのパス(/user/acto r -Name、actorNameはサーバー上で

actorOfを使用してアクターを作成した際に渡した文字列)このコードをビルドすると、システム

の依存先の.jarファイルが追加されます。特に、akka-remote JARと、netty-

3.5.3.Final.jarおよびprotobuf-java-2.4.1.jar(akka-2.0.3リリースより)という外部ライブラリへの依存関係に注意が必要です。すべての依存先のファイルはAkkaディストリビューションに含まれ

akka { actor { provider = "akka.remote.RemoteActorRefProvider" } remote { transport = "akka.remote.netty.NettyRemoteTransport" netty { hostname = "127.0.0.1" port = 2552 } }}

LISTING 15 LISTING 16 LISTING 17

Download all listings in this issue as text

Page 7: Akkaフレームワークを使用したアクターベースの …...プルと後半のPiを計算するサンプ ルを使用して、アクターベースの システムの設計方法について少し

ORACLE.COM/JAVAMAGAZINE // JANUARY/FEBRUARY 2013

JAVA TECH

82

COMMUNITY

JAVA IN

ACTION

ABOUT US

blog

//polyglot programmer / ますが、通常はビルド・パス内か、統合開発環境のプロジェクト設定で参照する必要があります。

まとめAkkaはどう考えてみても、すぐに習得できるような簡易なシステムではありません。何も共有しない環境でメッセージを受け渡しするという考え方は簡単です。しかし、アクター指向システムへの移行には困難を伴うでしょう。単方向のメッセージを送信すること、ブロックの発生を避けること、忘れずに失敗させること。これらのことに常に目を向けてください。 </article>

LEARN MORE• AkkaのWebサイト• [Java Champion Jonas Bonér Explains the Akka Platform」

object Client { def main(args: Array[String]): Unit = { val config = """akka { actor { provider = "akka.remote.RemoteActorRefProvider" } remote { transport = "akka.remote.netty.NettyRemoteTransport" netty { hostname = "127.0.0.1" port = 2553 } }}"""

val system = ActorSystem("client", ConfigFactory.parseString(config))

val actor = system.actorFor( "akka://[email protected]:2552/user/actorName") println("We have an actor: " + actor) actor ! "HELLO!!" println("Message sent") }}

LISTING 18

Download all listings in this issue as text