ØMQ(zeromq)について調査する。
ØMQ(zeromq)について簡単に調査したのでメモ。元ネタはØMQ - The Guide。
概要
- N-N通信を実現する、socket API風軽量メッセージングライブラリ。
- 自動的な再接続や、メッセージのキューイングを行ってくれる。
- 複数のメッセージングパターンと呼ばれるものを組み合わせることによって、柔軟なメッセージ配信を行うことができる。
ライブラリについて
- socket APIライクなC APIを持つ。以下socketは、zeromqのsocketを指す。
- zeromqはコンテキストというものを通じて使う。1コンテキストに、I/Oスレッドが1つ割り当てられる。基本1プロセスに1コンテキストでOK。複数のcontextを持つことはできるし、その場合は同じ個数のI/Oスレッドが走る。
- zeromqのsocketは、プロセス内通信(スレッド間通信など)、プロセス間通信、TCP、UDPマルチキャストをラップしたものである。icp:my.serverや、tcp:127.0.0.1:1234のようにしてラップ先を設定できる。
- 通信では、メッセージをやりとりする。メッセージは複数のフレームからなる。1つのフレームは、1つの長さ付きバイナリを持つ。
- 1つのsocketで、N-N通信ができる。
- 1つのノードは、複数のsocketを持つことができる。
- zeromqのsocketは本物のsocketとは違い、クライアントを先に起動することができる。
- 接続には、メッセージングパターンというものが設定されている。パターンによってメッセージ送受信方法が変わる。後述。
- サーバはデータの送信者、クライアントはデータの受信者では「ない」。あくまで、メッセージングパターンに基づいてメッセージがやりとりされる。
- ノードは、動的に追加できる。
- メッセージングパターンが用いるメタ情報を除いて、zeromqはメッセージの内容を関知しない。
キューについて
- メッセージは、メッセージングパターンに応じて送信時・受信時にキューイングされる。
- キューはロックレスで、基本オンメモリ。
- 送信キューにたまったメッセージは、I/Oスレッドが送信してくれる。非同期I/Oが用いられる。
- メッセージ送信APIはただ単にメッセージを送信キューに入れるだけ。
- I/Oスレッドが、メッセージは受信キューに受信し続けてくれる。非同期I/Oが用いられる。
- メッセージ受信APIはただ単にメッセージを受信キューから読み出すだけ。
- キューのサイズを指定することができる。
- ZMQ_SWAPオプションで、オンメモリ上のキューからあふれたメッセージをディスク上に書き出すことができる。ただし、そのファイルを再利用することはできない。あくまで、一時的な領域としてディスクを使うだけ。
メッセージングパターンについて
- REQ/REP/PUB/SUB/PUSH/PULL/XREQ/XREP/PAIRがある。
REQ/REPについて
- request-replyモデル。リクエストを行い、そのレスポンスを受け取る通信方式。
- REQがリクエストを行う側。複数のノードと接続している場合には、そのうち1つのノードにリクエストメッセージを送信する。
- REPがレスポンスを返す側。リクエストメッセージを送ってきたノードにレスポンスメッセージを送信する。
- REQ側はレスポンスを受け取るまで新たなリクエストを出来ないし、REP側はレスポンスを返すまで他のリクエストを受け取れないようになっている。
- これらの問題を解決するXREQ/XREPについては後述。
PUB/SUBについて
- publish-subscribeモデル。publish側がメッセージを流しつづけ、それを複数のsubscribe側が受けとることができる。
- メッセージにはkey部分とdata部分がある。
- PUB側は、key部分とdata部分を送る。
- SUB側は、特定のkeyを先頭に持つメッセージを受信する。複数のkeyについてメッセージを受信できる。
- SUB側は、keyを指定しないとメッセージを受け取ることができない。空文字列をkeyとして指定した場合、すべてのメッセージを受け取ることができる。
- key部分とdata部分は、同じフレーム内にあってもよい。ただし、keyの誤マッチが起こる可能性がある。keyとdataでフレームを分けることによって、誤マッチは避けることができる。
- PUB側がサーバである必要はない。SUB側がサーバとなることもできる。
- PUB側はデータ送信キューを持つ。キューがいっぱいの場合には、キューへの投入はただ無視される。
- SUB側はデータ受信キューを持つ。
PUSH/PULLについて
- pipelineモデル。PUB/SUBとは異なり、メッセージの受取り手はノード群のうちの1つのノードとなる。
- PUSH側がサーバである必要はない。PULL側がサーバとなることもできる。
- 複数PULLの場合には、ロードバランシングによってそれぞれのPULL側にメッセージが割り振られる。
- 複数PUSHの場合には、PULL側ではそれぞれのPUSH側から1つずつメッセージを順番に読むような感じになる。
- PUSH側はデータ送信キューを持つ。初めて接続されたPULL側にそれまで送ったメッセージが一気に送信されてしまう。複数のPULL側がいる場合には、それらをすべて起動してからPUSH側を立ち上げること。
PAIRについて
- 同期のために用いる。1-1接続のみ。
- PUSH/PULL、XREQ/XREP、PUB/SUBを同期目的に使うのは問題がある。
REQ/REP/XREQ/XREPについて
- XREQ/XREPを使うことによって、メッセージをルーティングすることができる。
- REQは、リクエストするメッセージの先頭に空のフレームを付与し、受け取ったレスポンスの先頭にある空のフレームを削除する。
- REPは、受け取ったリクエストメッセージの、先頭から空のフレームまでのフレームを保存する。送信するレスポンスの先頭に、保存したフレーム群を付与する。
- よって、REQ/REPの組み合わせでは、REQ側が付与した空のフレームをREP側がそのまま返却し、REQ側がその空のフレームを取り除くことになる。
- XREQは、接続しているすべてのノードに対してロードバランシングをしたリクエストを送り、それらのノード群からレスポンスを受け取る。
- XREQによる、リクエスト送信とレスポンス受信は対応していない。XREQはPUSH/PULLを兼ねたイメージで捉えるとよい。
- XREQは、メッセージ中のフレーム操作を行わない。
- XREPは、リクエストされたメッセージの先頭に、接続相手のノードIDが入ったフレームを付与する
- ノードIDはXREPの相手側が明示的に指定することができる。
- ノードIDが指定されない場合には、UUIDが使われる。
- XREPは、レスポンスメッセージの先頭のフレームを削除し、そのフレームに書いてあったノードIDにのみレスポンスメッセージを返却する。
- 多段ルーティングなども、これらの仕組みを組み合わせて実現される。
デバイスとは
- zeromqでは、上記のメッセージングパターンを組み合わせることによって、柔軟なネットワークを構成することができる。
- メッセージングパターン同士を仲介するノードを、デバイスと呼ぶ。
- zeromqは、いくつかの組み込みデバイスを持つ。
- 組み込みデバイスQUEUEは、REQから受け取ったリクエストを、複数のREPに送信し、その結果をREQに返すデバイス。
- 組み込みデバイスFORWARDERは、PUB/SUBを再送信する。PUB/SUBをスケールさせるには、木構造通信を行う必要がある。そのときに、根ノード以外の内部ノードとして用いることができる。
- 組み込みデバイスSTREAMERは、PUSH/PULLでFORWARDERの役割を果たすものである。
- メッセージングパターンの組み合わせとして、PUB-SUB,REQ-REP,REQ-XREP,XREQ-REP,XREQ-XREP,XREQ-XREQ,XREP-XREP,PUSH-PULL,PAIR-PAIRが存在する。
- これらのメッセージングパターンを組み合わせて、さまざまなトポロジやルーティングなどを実現することができる。
その他俺メモ
- Debianの公式パッケージになっていない。ITPは出ているので、じきに入るだろう。
- Debianの場合、ビルドにuuid-devパッケージもしくはe2fslibs-devが必要。uuid-devは、e2fslibs内で使われているUUID生成ライブラリlibuuidだけをパッケージとしたもの。
- 足周りのTCPやIPCは完全に隠蔽される。横からいじることはできない。
- メッセージングパターンの組み合わせは、http://zguide.zeromq.org/chapter:all を参考にすること。
- メッセージングパターンの複雑な組み合わせ例として、http://ipython.scipy.org/doc/nightly/html/development/parallel_connections.html が挙げられる。(2013年8月27日現在リンク切れ)
感想
- 簡単にN-N通信が出来るのは魅力。APIの数も少ないし、各種言語バインディングがそろっているのもいい。
- その名前からか、「キュー」という紹介のされ方をされている場合がある。冒頭に述べたように、N-N socket通信ライブラリと捉えたほうが適切ではないか。確かにキューイングはしてくれるけど。
- キューは永続化されない。REQ/REPの組み合わせでレスポンスを確認したりして、データが確実に先方に伝わったことを確認しない場合は、データは消えてもいい覚悟で使うのがいいだろう。
- メッセージングパターンの組み合わせで柔軟な通信は出来そうなのはわかったが、具体的なレシピがいっぱいあるとうれしい。
- tcp:での利用が多いんだろうが、inproc:やipc:も使いでがありそう。
- PUB/SUBでSUB側がサーバになれるのがうれしい。例えば、ログ収集サーバとしてSUB側を立て、動的にPUB側を増やすことが出来る。この用途で使ってみたいと考える。んでも、PUB/SUBでSUB側が1台しかないんだったら、PUSH/PULLでも充分だと今気づいた。
- PythonやRubyバインディングで遊んでみると理解が早い。C言語レベルで使いやすいAPIなので、LLでトポロジ設計をしたあとにC言語でガッツリ書く、というポリシーだとハイパフォーマンスな用途にも簡単に使えそうだ。