RabbitMQ Highly Available Queues and Clustering using Amazon EC2 という AWS 環境での RabbitMQ クラスタ構築に関する面白い記事を見かけた。
知識の再確認のため、以下をメモ。
- RabbitMQ のクラスタの構築
- HA:Active-Standby(Shared Nothing)
- HA:Active-Standby(Shared Storage)
- HA:Active-Active(Mirrored Queue)
RabbitMQ のクラスタリングの特徴
- Erlang/OTPの 分散フレームワークをベースにしている。
- ノード間でCookie(/var/lib/rabbitmq/.erlang.cookie)を同じにする
- ノード名(デフォルトはホスト名)をクラスタ間で重複しないようにする
- キューのメタ情報とキューが管理されているノードの場所はクラスタの各ノードで共有される
- キューの実データは管理している1ノードにしか存在しない
- 各ノードはRAMノードとDiskノードのどちらかを選べる
- Diskノードはメタ情報をRAMだけでなくディスクにも保存する。
- クラスタ全体で少なくとも1ノードはDiskノードでなければいけない。
- クラスタ内のすべてのDiskノードが落ちている場合、多くの更新系処理が行えなくなる
事前準備
WebUI を有効にする
クラスタステータスはWebUI から確認するとわかりやすいので有効にしておく。http://HOST:55672/ のアドレスから管理画面にアクセスできる(デフォルトのID/password は guest/guest)
george@u1204a:~/rabbitmq_server-2.8.7$ sudo rabbitmq-plugins list [ ] amqp_client 2.8.7 [ ] eldap 2.8.7-gite309de4 [ ] erlando 2.8.7 [ ] mochiweb 2.3.1-rmq2.8.7-gitd541e9a [ ] rabbitmq_auth_backend_ldap 2.8.7 [ ] rabbitmq_auth_mechanism_ssl 2.8.7 [ ] rabbitmq_consistent_hash_exchange 2.8.7 [ ] rabbitmq_federation 2.8.7 [ ] rabbitmq_federation_management 2.8.7 [ ] rabbitmq_jsonrpc 2.8.7 [ ] rabbitmq_jsonrpc_channel 2.8.7 [ ] rabbitmq_jsonrpc_channel_examples 2.8.7 [ ] rabbitmq_management 2.8.7 [ ] rabbitmq_management_agent 2.8.7 [ ] rabbitmq_management_visualiser 2.8.7 [ ] rabbitmq_mochiweb 2.8.7 [ ] rabbitmq_shovel 2.8.7 [ ] rabbitmq_shovel_management 2.8.7 [ ] rabbitmq_stomp 2.8.7 [ ] rabbitmq_tracing 2.8.7 [ ] rfc4627_jsonrpc 2.8.7-gita5e7ad7 [ ] webmachine 1.9.1-rmq2.8.7-git52e62bc $ sudo rabbitmq-plugins enable rabbitmq_management The following plugins have been enabled: mochiweb webmachine rabbitmq_mochiweb amqp_client rabbitmq_management_agent rabbitmq_management Plugin configuration has changed. Restart RabbitMQ for changes to take effect. $ sudo /etc/init.d/rabbitmq-server restart
Cookie を揃える
各ノードで /var/lib/rabbitmq/.erlang.cookie を同じにする。
1. RabbitMQのクラスタの構築
3ノード(u1204a, u1204b, u1204c)のクラスタを組んでみる
初期状態
u1204a だけの状態
rabbitmq@u1204a:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204a ... [{nodes,[{disc,[rabbit@u1204a]}]},{running_nodes,[rabbit@u1204a]}] ...done.
RAMノードのクラスタを追加
cluster の引数に JOIN 先ノード名(rabbit@u1204a)を指定。
$ rabbitmqctl cluster rabbit@u1204a Clustering node rabbit@u1204b with [rabbit@u1204a] ... ...done. rabbitmq@u1204b:~$ rabbitmqctl start_app Starting node rabbit@u1204b ... ...done. rabbitmq@u1204b:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204b ... [{nodes,[{disc,[rabbit@u1204a]},{ram,[rabbit@u1204b]}]}, {running_nodes,[rabbit@u1204a,rabbit@u1204b]}] ...done.
diskノードをクラスタに追加
RAM ノードの場合とことなり、cluster の引数に自分自身のノード名(rabbit@u1204c)も含める
rabbitmq@u1204c:~$ rabbitmqctl cluster rabbit@u1204a rabbit@u1204c Clustering node rabbit@u1204c with [rabbit@u1204a,rabbit@u1204c] ... ...done. rabbitmq@u1204c:~$ rabbitmqctl start_app Starting node rabbit@u1204c ... ...done. rabbitmq@u1204c:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204c ... [{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]}, {running_nodes,[rabbit@u1204a,rabbit@u1204b,rabbit@u1204c]}] ...done.
Web UI を確認
ノードをひとつ落とす
ノード u1204c を落とします。-n 引数で操作対象のノード名を指定します。
rabbitmq@u1204a:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204a ... [{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]}, {running_nodes,[rabbit@u1204b,rabbit@u1204c,rabbit@u1204a]}] ...done. rabbitmq@u1204a:~$ rabbitmqctl -n rabbit@u1204c stop_app Stopping node rabbit@u1204c ... ...done. rabbitmq@u1204a:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204a ... [{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]},{ram,[rabbit@u1204b]}]}, {running_nodes,[rabbit@u1204b,rabbit@u1204a]}] ...done.
running_nodes から u1204c が消えています。
クラスタからノードを外す
u1204b をクラスタから外します。rabbitmqctl stop したあとで rabbitmqctl reset します。
rabbitmq@u1204b:~$ rabbitmqctl stop_app Stopping node rabbit@u1204b ... ...done. rabbitmq@u1204b:~$ rabbitmqctl reset Resetting node rabbit@u1204b ... ...done. rabbitmq@u1204a:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204a ... [{nodes,[{disc,[rabbit@u1204c,rabbit@u1204a]}]}, {running_nodes,[rabbit@u1204c,rabbit@u1204a]}] ...done.
RAM ノードとして追加されていた u1204b がノード一覧から消えています。
よくあるエラーパターン
ノード操作時のよくあるパターンをまとめてみた。
stop_app するまえに reset した場合
$ rabbitmqctl reset Resetting node rabbit@u1204a ... Error: mnesia_unexpectedly_running $ rabbitmqctl stop_app Stopping node rabbit@u1204a ... ...done.
クッキーがjoin先クラスタと違っている場合
$ rabbitmqctl cluster rabbit@u1204a Clustering node rabbit@u1204b with [rabbit@u1204a] ... Error: unable to connect to node rabbit@u1204b: nodedown DIAGNOSTICS =========== nodes in question: [rabbit@u1204b] hosts, their running nodes and ports: - u1204b: [{rabbit,40144},{rabbitmqctl3334,51254}] current node details: - node name: rabbitmqctl3334@u1204b - home dir: /var/lib/rabbitmq - cookie hash: 2ePLuBjG+y15B4w0gBRFwg==
Erlang のバージョンが異なっている場合
わかりにくいエーラが出力される。
http://rabbitmq.1065348.n5.nabble.com/Unable-to-cluster-td12282.html
$ rabbitmqctl cluster rabbit@u1204a Clustering node rabbit@u1204c with [rabbit@u1204a] ... Error: {unable_to_join_cluster, [rabbit@u1204a], {aborted, {function_clause, [{mnesia_schema,cs2list, [{cstruct,schema,set, [rabbit@u1204b], [rabbit@u1204a], [],0,read_write,false,[],[],false,schema, [table,cstruct], [],[], {{1351,317797,344820},rabbit@u1204a}, {{11,0},{rabbit@u1204b,{1351,318892,577271}}}}]}, {mnesia_schema,do_merge_schema,1}, {mnesia_tm,apply_fun,3}, {mnesia_tm,execute_transaction,5}, {mnesia_schema,schema_coordinator,3}]}}}
RabbitMQのバージョンが異なる場合
$ rabbitmqctl cluster rabbit@u1204a Clustering node rabbit@u1204b with [rabbit@u1204a] ... Error: {version_mismatch,[add_ip_to_listener,exchange_event_serial, exchange_scratch,gm,ha_mirrors,mirrored_supervisor, remove_user_scope,semi_durable_route,topic_trie, user_admin_to_tags,add_queue_ttl, multiple_routing_keys], [add_ip_to_listener,exchange_event_serial, exchange_scratch,gm,ha_mirrors,mirrored_supervisor, remove_user_scope,semi_durable_route,topic_trie, topic_trie_node,user_admin_to_tags,add_queue_ttl, multiple_routing_keys]}
マイナーバージョンだけが異なる場合は、管理画面で警告メッセージが表示される。
- RabbitMQ : Clustering Guide
http://www.rabbitmq.com/clustering.html - RabbitMQ In Action : Ch.5. Clustering and dealing with failure
2. High Availability : Active-Standby 構成(Shared Nothing)
※画像は RabbitMQ In Action から
HAProxy を利用して、お手軽に Shared Nothing な Active-Standby 構成を構築。
Active-Standby 構成(Shared Nothing)の特徴
- フェイルオーバー時の接続先変更をHAProxyで行う
- データは共有されておらず 各 RabbitMQ で閉じている
- Active のデータが壊れていても、Standby機には影響なし
- RabbitMQ のバージョンがActive-Standby で異なっていても問題ない。
- Activeノードが復活すれば、キューは再び Active 側に振り分けられるようになる。
“RabbitMQ In Action” では1つのマシン内に HAProxyと ノード名とポートをかえた2つの RabbitMQ を起動して、Active-Standby を構築していた。
以下の /etc/haproxy/haproxy.cfg では HAProxy と u1204a/u1204c の 2つの RabbitMQ がそれぞれ別マシンであることを前提にしている。
global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 4096 user haproxy group haproxy daemon defaults log global mode tcp option tcplog option dontlognull listen rabbitmq_local_cluster 127.0.0.1:5672 mode tcp balance roundrobin server u1204a u1204a:5672 check inter 5000 rise 2 fall 3 server u1204c u1204c:5672 backup check inter 5000 rise 2 fall 3 listen private_monitoring :8101 mode http option httplog stats enable stats uri /stats stats refresh 5s listen private_monitoring :8101 mode http option httplog stats enable stats uri /stats stats refresh 5s
あとは u1204a を $ rabbitmqctl stop_app/start_app しつつキューを投げると、failover 時の処理を確認できる。
HAProxy の Web 管理画面は次のURL で確認可能 : http://HOST:8101/stats
キャプチャの赤枠を見るとわかるように、ノード u1204a は Ack が”Y(es)”, ノード u1204c は Bck が”Y(es)” になっている。
参照
- RabbitMQ In Action : Ch. 7. Warrens and Shovels: failover and replication
3. High Availability : Active-Standby 構成(Shared Storage)
分散ストレージを利用して Active-Standby な HA 構成を組む。
※画像は RabbitMQ In Action から
RabbitMQ のサイトのドキュメントでは、この構成の代わりに Active-Active を使えと目立つように書かれている。
High availability with Pacemaker and DRBD
This page documents a legacy technique for achieving active-passive high availability with RabbitMQ. Active-active mirrored queues are easier to use and do not impose a delay at failover.
Active-Standby 構成(Shared Storage)の特徴
- 各RabbitMQ のノード名と Cookie を揃えておく
- DRBD を共有ディスクに利用
- Corosync をクラスタ制御に利用
- pacemaker で RabbitMQ と DRBD クラスタのリソース管理をする
- 1+1構成と2+1構成の両方を解説
未検証
参照
- RabbitMQ : High availability with Pacemaker and DRBD
http://www.rabbitmq.com/pacemaker.html - RabbitMQ In Action : Ch. 7. Warrens and Shovels: failover and replication
- gihyo.jp > ADMINISTRATOR STAGE > 連載 > Pacemakerでかんたんクラスタリング体験してみよう!
http://gihyo.jp/admin/serial/01/pacemaker
4. High Availability : Active-Active構成
active-active HA は RabbitMQ 2.6 で導入された。
※画像は RabbitMQ In Action から
active-active HA の特徴
- キューの “x-ha-policy” 属性を “all”(または “node”)で宣言する。
- all: This policy ensures that a mirror of the queue will exist on every node in the cluster.
- nodes: This policy allows you to specify the exact nodes on which you wish mirrors to exist.
- 非active-active HA で宣言されたキューを active-active にすることはできない
- ノードの追加はあとからでもOK
mirrored-queue を構築
まずは 2 diskノードなクラスタを組む。
rabbitmq@u1204a:~$ rabbitmqctl stop_app Stopping node rabbit@u1204a ... ...done. rabbitmq@u1204a:~$ rabbitmqctl reset Resetting node rabbit@u1204a ... ...done. rabbitmq@u1204a:~$ rabbitmqctl start_app Starting node rabbit@u1204a ... ...done.
2台目を disk ノードで追加
rabbitmq@u1204b:~$ rabbitmqctl stop_app Stopping node rabbit@u1204b ... ...done. rabbitmq@u1204b:~$ rabbitmqctl reset Resetting node rabbit@u1204b ... ...done. rabbitmq@u1204b:~$ rabbitmqctl cluster rabbit@u1204a rabbit@u1204b Clustering node rabbit@u1204b with [rabbit@u1204a,rabbit@u1204b] ... ...done. rabbitmq@u1204b:~$ rabbitmqctl start_app Starting node rabbit@u1204b ... ...done. rabbitmq@u1204b:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204b ... [{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]}, {running_nodes,[rabbit@u1204a,rabbit@u1204b]}] ...done.
キューを追加
rabbitmq@u1204a:~$ python publish.py publish complete rabbitmq@u1204a:~$ python publish.py publish complete
Web管理画面でキューを確認
キューの Parameters に “HA” の文字がある。
キューの詳細を見ると、Parameters に “x-ha-policy” があり、Node と Mirrors に active-active のノード名がある。
片方を落とす
u1204a を落として片肺運転にする
rabbitmq@u1204a:~$ rabbitmqctl stop_app Stopping node rabbit@u1204a ... ...done. rabbitmq@u1204b:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204b ... [{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]}, {running_nodes,[rabbit@u1204b]}] ...done.
ノード「rabbit@u1204b」だけが running 状態。
u1204b だけの片肺状態でキューを追加
rabbitmq@u1204b:~$ python publish.py publish complete rabbitmq@u1204b:~$ rabbitmqctl list_queues Listing queues ... test_queue 3 ...done.
u1204a を復活させる
rabbitmq@u1204a:~$ rabbitmqctl start_app Starting node rabbit@u1204a ... ...done. rabbitmq@u1204a:~$ rabbitmqctl cluster_status Cluster status of node rabbit@u1204a ... [{nodes,[{disc,[rabbit@u1204b,rabbit@u1204a]}]}, {running_nodes,[rabbit@u1204b,rabbit@u1204a]}] ...done. rabbitmq@u1204a:~$ rabbitmqctl list_queues Listing queues ... test_queue 3 ...done.
キューを確認すると、3つと正しいが、管理画面では何やらメッセージが表示されている。
- 青(+0) : There are no synchronised mirrors
- 赤(+1) : Unsynchronised mirrors: rabbit@u1204a
u1204a ノードの復活後も、キューの同期が行われていない。
試しに、この状態で u1204b を停止させて、u1204a でキューを確認すると
rabbitmq@u1204b:~$ rabbitmqctl stop_app Stopping node rabbit@u1204b ... ...done. rabbitmq@u1204a:~$ rabbitmqctl list_queues Listing queues ... test_queue 0 ...done.
理由は Highly Available Queues#Unsynchronised Slaves に書かれてある。
A node may join a cluster at any time. Depending on the configuration of a queue, when a node joins a cluster, queues may add a slave on the new node. At this point, the new slave will be empty: it will not contain any existing contents of the queue, and currently, there is no synchronisation protocol.
…
Thus a newly added slave provides no additional form of redundancy or availability of the queue’s contents until the contents of the queue that existed before the slave was added have been removed.
ノードの復活はクラスタから見ればノードの追加と同じ扱い。
ノード追加前のキューがさばかれないと、同期は行われない仕組みになっている。
Mirrored Queue 用のコード
from pika.adapters import BlockingConnection | |
connection = BlockingConnection() | |
channel = connection.channel() | |
def callback(ch, method, properties, body): | |
print " [x] Received %r" % (body,) | |
channel.basic_consume(callback, | |
queue='test_queue', | |
no_ack=True) | |
channel.start_consuming() |
# http://karlgrz.blogspot.jp/2012/10/rabbitmq-highly-available-queues-and.html | |
from pika.adapters import BlockingConnection | |
from pika import BasicProperties | |
connection = BlockingConnection() | |
channel = connection.channel() | |
client_params = {"x-ha-policy": "all"} | |
exchange_name = 'public' | |
queue_name = 'test_queue' | |
routing_key = 'test_routing_key' | |
channel.exchange_declare(exchange=exchange_name, type='topic') | |
channel.queue_declare(queue=queue_name, durable=True, arguments=client_params ) | |
channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key=routing_key) | |
channel.basic_publish(exchange=exchange_name, routing_key=routing_key, body='testing mirroring!', properties=BasicProperties(content_type="text/plain", delivery_mode=2)) | |
# delivery_mode 1 : nonpersistent | |
# delivery_mode 2 : persistent | |
print "publish complete" | |
connection.close() |
参照
- RabbitMQ : Highly Available Queues
http://www.rabbitmq.com/ha.html - RabbitMQ In Action : Ch.5. Clustering and dealing with failure
- http://d.hatena.ne.jp/cooldaemon/20110902/
One thought on “RabbitMQ(2.8向け)のHAクラスタ構成”