メリークリスマス。Elixir Advent Calendar 2013 24日目ですよ。
Elixirが大好きなみんなは、ErlangやRubyにも興味があると思うんですよね。ああ、もしかしたらScalaにも興味があるかもしれませんね。
RubyでElixir/Erlang/ScalaのようなActor modelを実現しようとしたら、Celluloidが定石かなと思いますし、最近はconcurrent-rubyというのも出てきましたが、でもCRubyのマルチスレッドは実質シングルスレッドだったりして(CRuby 2.0時点)、そのままでは計算機リソースを活かすことが難しいです。
というわけで先週日曜に半日かけて一通り作りました。
actoryとは
RubyでActor modelや並列処理を実現するためのフレームワークです。actoryはActor model like, concurrent and distributed framework for Ruby
の略で、Oxford English Dictionaryによると、An agent , esp. an administrator ; a person who acts on behalf of another .
とかA person who performs or takes part in any action; a doer.
とかいう意味合いです。
特徴は4つ。
- Actor modelっぽいMessage PassingができるAPIを提供します。
- プラグインは動的ローディング対応です。Erlangからインスパイヤ。
- 出来る限り並列化します。ジョブスケジューラはまだ単純ですが。。。
- オーバーヘッド少なめ。通信&シリアライズ部分にMessagePack RPCを使っています。
用語集
- Sender
- actoryの送信側。
- Receiver
- actoryの受信側。
- actor
- Receiverとほぼイコール。
- Receiverについて、Senderを意識しない文脈や、Actor modelを意識する文脈において、こう呼ぶ。
設計思想
Message Passing
Elixirとは全体的に異なります。
Elixirの<-演算子に相当する部分は、Senderで実行するmessageインスタンスメソッドとして実装しました。
ただ、Elixirで関数自体を渡すようにブロックをProcオブジェクトとして渡すことはできません。この処理はMessagePack RPCとの相性が悪く、結局のところ、渡される関数に相当する部分はプラグインの動的ローディングでカバーすることとし、Receiverで実行するメソッド名と処理対象のオブジェクト(テキストベース)を渡すという形にしました。
Receiverについても、Elixirにおけるreceive文とは、かなり異なります。
actoryでは、Actory::Receiver::EventHandlerのreceiveメソッドとして実装していますが、Elixirのreceive文におけるcase文のような振る舞い、すなわちactoryにおいて渡されたデータを見てどのプラグインのメソッドを実行するかは、Rubyの方に任せています。
処理が終わって値を返したらまた待受状態に戻るという点では、Scalaに近いかもしれません。
プラグイン
専用のディレクトリに突っ込んでおけば自動的にロードされるようにしています。
作り方としては、Actory::Receiver::Pluginとしてメソッドを定義すればオーケー。Rubyのクラスは再オープンできるので便利です。
また、Receiver自体の機能も、できるだけプラグインで実現するようにしています。
並列処理
Parallelを使っています。クライアントがSenderに渡した処理対象のデータ(配列)をバラして、それぞれをactorに振り分けています。
SenderもReceiverも、基本的にCPUコア数の分だけプロセスを生成します。これらのプロセスをどのCPUコアが処理するかはOSのジョブスケジューラ任せにしています。
通信
プロセス間通信は、MessagePack RPCを使用しています。シリアライズの高速性と扱いやすさが決め手です。
インストールとか使い方とかアーキテクチャとかプラグインの書き方とか
こちらをご覧下さい。
https://github.com/keithseahus/actory
課題
- テストを書く
- 半日+αくらいで、勢いだけで作ったので…。仕事ではちゃんとテスト書いてますよ(迫真)
- 戻り値の形式改善
- もう少し扱いやすい形式があるように思える。
- ジョブスケジューラの追加(1)
- actorに127.0.0.1のホストがいる場合、リモートのReceiverに投げずに、ローカルで処理した方が早い処理もある。そのためのスケジューラの追加。
- ジョブスケジューラの追加(2)
- actorの計算機リソースの状況を取得するプラグインは既に作ってあるので、それを使ったロードバランシングができるスケジューラが欲しいところ。ScalaのAkkaルータ的なものを実現したい。
- メッセージング方法の柔軟性向上
- ネットワークで例えるとユニキャスト,マルチキャスト,ブロードキャスト的な制御を任意に切り替えられるようにしたい。
- メールボックス
- Receiverに対するキューイング処理が欲しい。
- Senderについても、Receiverからの返却を非同期で受け取って取り出せる機構が欲しい。
- ゼロ・コンフィギュレーション
- 設定ファイルでactorを列挙してしまっている時点でイケてない。
まとめ
- Actor modelが好きすぎて勢いだけでactoryというものを作りました。
- CRuby縛りの開発でActor modelと並列処理が欲しい時に、意外と使えるかも?
- 明日は最終日、 @mururu さんによる完走です。