developing an akka edge6
TRANSCRIPT
Putting actors to workアクターを使うのにまず必要な事
・akka.actor.Actorトレイトを継承したクラスと、そこにreceiveメソッドを定義
・ActorSystemを作る
・ActorSystemからアクターを生成
メッセージの送り方② ask
Futureを返す
Futureが含んでいる値(処理済/まだ処理されていない/永遠に処理されない)
永遠に処理されない可能性もあるので、implicit valを使って時間を設定
アクターへ送るメッセージは非同期なので、アクター以外のコード内でレスポンスを得るにはFutureを使う必要がある
Developing an Akka Edgeだけでは理解できない部分があったため、
AkkaとJavaのDocumentLet it crashAkka ConcurrencyJava言語で学ぶデザインパターン入門
も参考にしました。
(今回はもはや本の要約じゃないです)
これまでと同様に
自分が持っていた知識やいくつかの情報を元にまとめたり、
不慣れな英語を読み進めているため正しくない項目がある可能性があります。
Chapter 6.Dispatchers
*この章のポイント*
・いろんなDispatcherといろんなパラメータ
2つのexecutor(Fork/JoinとThreadPoolExecutor)
・いろんなMailboxとパラメータ
・ざっくりした使用例とチューニングについて
Chapter 6.DispatchersDispatcherとRouterは全然別物!!
Dispatcherのコンセプト
アクターが処理を実行する時のスレッド割当
スレッドプールを持っていて、スレッドをアクターに割り当てる
Bulkhead Pattern例:IO処理と計算処理のActor達を別のDispatcherにすると…
計算処理Dispatcherのスレッド数を多くするなど
IO と 計算のどっちに比重を置くかチューニングができる
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← Dispatcherの名前
後でコード上から呼び出す時に使う。好きな名前で
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← Dispatcherの種類
DispatcherPinnedDispatcherBalancingDispatcherCallingThreadDispatcher
から選ぶ
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
←executorの種類
fork-join-executorthread-pool-executorから選ぶ
並列処理の実行の仕方が違う
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← throughput
この値は、Actorにスレッドを割り当てた時1回あたりいくつのメッセージを処理するか(終わったらスレッドプールに戻る)
throughputとfairnessthroughput = 1の時
Actor1のメッセージを1つ処理
Actor2の…Actor3の…Actor1の…
Actor3のMailboxが最初に空になる
throughputとfairnessthroughput = 100の時
Actor1のメッセージを100つ処理
Actor2の…Actor3の…
Actor1のMailboxが最初に空になる
throughput = 1だと公平にメッセージを処理できるが、
切り替えが頻繁に起こるとパフォーマンスが悪くなる
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← このへんはスレッド数の設定的な感じ
thread-pool-executorを使う時は別の項目
Fork/Joinとは
タスクを分割していって、それらを並列に処理していくフレームワーク
Java7から導入された
a + b = e c + d = f
a + b + c + d
ちゃんと理解してないので雑な説明 …
Fork/Joinとは
タスクを分割していって、それらを並列に処理していくフレームワーク
Java7から導入された
e + f
a + b = e c + d = f
ちゃんと理解してないので雑な説明 …
fork-join-executorの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
CPUのコア数×factor⇒並列実行するスレッド数
ただし、min < スレッド数 < max
ThreadPoolExecutorとは
java.util.concurrent.ThreadPoolExecutorワーカスレッドを管理するクラス
タスクキューを持っていて
スレッドプールのスレッドを使ってタスクを実行
⇒終わったらスレッドプールにスレッドがもどる
というのを繰り返している
スレッド
thread-pool-executorの設定
my-thread-dispatcher{type=Dispatcherexecutor="thread-pool-executor"thread-pool-executor{
core-pool-min = 4core-pool-max = 32core-pool-factor = 2.0
max-pool-min = 8max-pool-max = 84max-pool-factor = 3.0
keep-alive-time = 30ms
task-queue-type = “linked”}
}
core-poolとmax-poolそれぞれの設定がある
corePoolとmaxPool新しいタスクが、キューに追加された時
①corePoolSize > 実行中のスレッド数
新しいスレッドが作成される
②corePoolSize < 実行中のスレッド数 < maxPoolSizeキューが一杯の時のみ新しいスレッドが作成される
③maxPoolSize == 実行中のスレッド数
キューが一杯ならタスクがrejectされる
keep-alive-timecorePoolSize < 実行中のスレッド数 の時
タスクキューが空で暇なスレッドがあったら…
アイドル状態がkeep-alive-timeを超えるとスレッドが終了する
task-queue-typeLinkedBlockingQueue(サイズ上限なし)
ArrayBlockingQueue(サイズ上限指定)
から選ぶ
※LinkedBlockingQueueを使うと、corePoolSizeの全てのスレッドがbusy状態の時
新しいタスクはキュー内にどんどんたまっていくので
corePoolSizeを超えるスレッドは作成されない※
大きなキューと小さなプールを使用すると、スループットが低下する恐れあり
(CPU使用率・context switchingのオーバーヘッドは最小化される)
thread-pool-executorの設定
my-thread-dispatcher{type=Dispatcherexecutor="thread-pool-executor"thread-pool-executor{
core-pool-min = 4core-pool-max = 32core-pool-factor = 2.0
max-pool-min = 8max-pool-max = 84max-pool-factor = 3.0
keep-alive-time = 30ms}
}
いろんなDispatcherDispatcher
デフォルトで使っている
PinnedDispatcher
BalancingDispatcher
CallinThreadDispatcherakka.testkitに入ってるので、9章で登場するかも!?
いろんなDispatcherDispatcher
デフォルトで使っている
PinnedDispatcher
BalancingDispatcher
CallinThreadDispatcherakka.testkitに入ってるので、9章で登場するかも!?
PinnedDispatcherそれぞれのアクターに1スレッドずつ割り当てる。
⇒全てのアクターがスレッドをフルに使える
特に優先度が高いリソースを持っている時に使う
※このパターンを使いすぎないように!!※
BalancingDispatcher1つのMailboxをシェアしている。
暇になったActorがメッセージを処理するので、負荷は公平
全て同じ型のActorと使う事を想定されている
(型が違っても、受け取れるメッセージが同じなら動きそう)
6種のMailboxUnboundedMailboxSingleConsumerOnlyUnboundedMailboxBoundedMailboxUnboundedPriorityMailboxBoundedPriorityMailboxDurable
6種のMailboxUnboundedMailboxSingleConsumerOnlyUnboundedMailboxBoundedMailboxUnboundedPriorityMailboxBoundedPriorityMailboxDurable 1つのActorに1つのMailbox
(デフォルトはこれ)
他のMailboxが全部シェアしているのかは未確認です …
6種のMailboxUnboundedMailboxSingleConsumerOnlyUnboundedMailboxBoundedMailboxUnboundedPriorityMailboxBoundedPriorityMailboxDurable Mailboxの容量が
有限 or 無限
6種のMailbox容量が有限の時のパラメータ
mailbox-capacityMailboxの容量
mailbox-push-timeout-timeMailboxが一杯の時、空くのを待つ時間(?)
-1にすると無限
6種のMailboxUnboundedMailboxSingleConsumerOnlyUnboundedMailboxBoundedMailboxUnboundedPriorityMailboxBoundedPriorityMailboxDurable Mailbox内のメッセージに
優先度付き
6種のMailboxUnboundedMailboxSingleConsumerOnlyUnboundedMailboxBoundedMailboxUnboundedPriorityMailboxBoundedPriorityMailboxDurable Mailboxのメッセージを
永続化できる(ファイルシステム)
Dispatcherを使う
①ActorにDispatcherを設定する場合
val myActor = system.actorOf(Props[MyActor].withDispatcher(“my-dispatcher”))
Actorの設定をconfigurationに書いていれば、withDispatcherを使わなくてもよい
val myActor = system.actorOf(Props[MyActor],”myactor”)
akka.actor.deployment{/myactor{
dispatcher = my-dispatcher}
}
Dispatcherを使う
②FutureのためにDispatcherを使う場合
implicit val executionContext = system.dispatchers.lookup(“my-dispatcher”)
Mailboxを使う
myMailbox{mailbox-type = “BoundedMailbox”mailbox-capacity = 1000mailbox-push-timeout-time = 1
}
val myActor = system.actorOf(Props[MyActor].withMailbox(“myMailbox”)
Dispatcherの時と同様に、akka.actor.deployのActorの設定内に書いてもよい
Low latency ⇒ PinnedDispatcherを使う
例:特定のActorに1スレッド丸ごと割り当てると…
スレッドが割り当てられるのを待たなくて良いので
メッセージをすぐ処理してくれる
Bulkhead Pattern ⇒ Dispatcherを使う
例:IO処理と計算処理のActor達を別のDispatcherにすると…
計算処理Dispatcherのスレッド数を多くするなど
IO と 計算のどっちに比重を置くかチューニングができる
Dispatcherの設定
デフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
なんやかんや言ってもデフォルト設定で良い場合が多い
Typesafe Consoleでチェック
Dispatcher view でメッセージハンドリングのlatency、mailbox-sizeなどが見れる
(ちゃんと調べてませんが、現在は無料で使えないようです…)
profilingしてheavy computationな部分を特定し
⇒RouterとBalancingDispatcherを使って外に出す
mailbox sizeが増え続けているならRouterを作った方がよい
デフォルトでもスレッド数の上限が決まっているので、
コア数を増やしても別のDispatcherを使わないと
futureがタイムアウトになったり、スループットが上がらない
typeデフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← Dispatcherの種類
最優先処理がある場合PinnedDispatcher(よっぽど特別な場合だけ使用する)
負荷分散したい時はRouterとBalancingDispatcher
executorデフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
←executorの種類
fork-join-executorの方が良い(PinnedDispatcherはthread-pool-のみ)
Let it crashの記事より、thread-poolからfork-joinにしたら1100%増になった
Throughput比較
task-queueへのアクセス/ロックが多い ⇒ context switchingが発生しやすい
throughputデフォルトの設定
default-dispatcher{type=Dispatcherexecutor="fork-join-executor"throughput=10
fork-join-executor{parallelism-min=4parallelism-max=32parallelism-factor=4.0
}}
← throughput
Actor数≒スレッド数なら多め時間のかかるタスクが多いなら少なめ(頻繁に切り替わると時間がかかる)
デフォルト値から初めてチューニングすべし