ZeroMQのheartbeatを試してみた

ZeroMQのversion 4.2からheartbeat機能が追加されました。 元々コネクションの切断は検知してzmq_socket_monitor(3)で通知してくれたのですが、 TCPエラーを吐かずにコネクションが死んだり、Out of Memory等でプロセスが止まったりした場合は検知できないので heartbeat機能が追加されたようです。(参考:ZeroMQのRFC

heartbeatに関するソケットのオプションは以下の3つです。

  • ZMQ_HEARTBEAT_IVL
  • ZMQ_HEARTBEAT_TIMEOUT
  • ZMQ_HEARTBEAT_TTL

ZMQ_HEARTBEAT_IVLミリ秒の間隔でpingを送り、ZMQ_HEARTBEAT_TIMEOUTミリ秒経っても pongが返ってこなかったらコネクションが死んだとみなすという感じです。また、pongを返す側では ZMQ_HEARTBEAT_TTLミリ秒経っても次のpingが来なかったらコネクションが死んだとみなします。 ZMQ_HEARTBEAT_IVLを設定することで初めてheartbeatが動作します。

試しに以下のような設定をしてみます。基本的にどちらか片方(connect側もしくはbind側)のソケットにだけ設定しておけばOKだと思います。

int interval = 100, timeout = 1000, ttl = 1000;
zmq_setsockopt(sock, ZMQ_HEARTBEAT_IVL, &interval, sizeof(int));
zmq_setsockopt(sock, ZMQ_HEARTBEAT_TIMEOUT, &timeout, sizeof(int));
zmq_setsockopt(sock, ZMQ_HEARTBEAT_TTL, &ttl, sizeof(int));
zmq_setsockopt(sock, ZMQ_HANDSHAKE_IVL, &timeout, sizeof(int));

ZMQ_HEARTBEAT_TTLZMQ_HEARTBEAT_IVLよりも十分大きい値にしておく必要があります。 そうしないと接続が頻繁に切れてしまいます。ZMQ_HEARTBEAT_TIMEOUTも短すぎると 偽陽性が増えてしまうので注意です。

また、ZMQ_HANDSHAKE_IVLを設定しています。ZeroMQではソケットが接続した後に ソケットの設定情報を交換する(これをhandshakeと呼ぶ)そうなのですが、止まっているソケットに再接続した際に ZMQ_HEARTBEAT_TIMEOUTよりもZMQ_HANDSHAKE_IVLが優先されるため、両方共同じ値にしておきます。 ZMQ_HANDSHAKE_IVLのデフォルト値は30秒なので、これを設定しないと ZMQ_HEARTBEAT_TIMEOUTをどれだけ短くしても再接続時に30秒待ってしまいます。

実際にこんな感じのコードを書いて動かします。 bind/connectが完了した時点で、connect側のプロセスにSIGTSTPを送るとbind側のzmq_socket_monitor(3)から ZMQ_EVENT_DISCONNECTEDが通知されます。逆に、bind側のプロセスにSIGTSTPを送ると、connect側の zmq_socket_monitor(3)からZMQ_EVENT_DISCONNECTEDが通知された後に、再接続を試みます。 bind側のlistenが残っているからか、再接続は成功しますが、handshakeが失敗して再びZMQ_EVENT_DISCONNECTEDが 通知されるというような挙動になります。

また、heartbeatを有効にしても前回の調査同様、unbind/disconnect時には 相手側のソケットには何も通知されません。disconnectしても再接続しようとするし、unbind/disconnectとは何なのだろう…。 とりあえずソケットを切断するときは必ずcloseした方が良さそうです。

※ZeroMQ v4.2.1を使用