はじめに
RabbitMQ の冗長化の機能として Clustering(クラスタリング)と QueueMirroring(Queueミラーリング)が提供されているようです。 今回は、これらを使って冗長化を考えてみたいと思います。
Cluster の Node では、Queue、Exchange、Binding、User などが共有できるようです。 しかし Queue だけは、他と扱いが異なるということです。 Queue は、Cluster 内のどこか1つの Node が HomeNode となり保持される仕組みになっているようです。 HomeNode 以外の Node は、Queue を参照する形になるようです。 そのため HomeNode が落ちてしまうと他の Node は、Queue の参照できるけれども Queue にあるメッセージが参照できなくなるという事態に陥ってしまうようです。 さらにメッセージが Persistence でなければメッセージ自体もなくなってしまう。 これをカバーするものが QueueMirroring となるようです。 HomeNode のメッセージを他の Node でも共有しようという仕組みのようです。
始める前に... RabbitMQ がインストール済であること RabbitMQ サーバーにログイン可能でリソースにアクセスできるユーザが作成済であること (ユーザとパスワードを "user01" と "passwd" としています。 ManagementPlugin が有効になっていて CommandLineTool がインストールていること 以上が前提になります。 このあたりはわたしも『yum の Local Repository に RabbitMQ をインストール』にまとめていますので よろしければ、こちらも合わせて、ご参考に。
ではでは!Getting Started!
環境
今回は、以下の2台のサーバー(vm-1st と vm-2nd)で冗長化構成を考えたいと思います。RabbitMQ Server (vm-1st)
ホスト名 | vm-1st |
---|---|
IP | 192.168.10.110 |
ミドルウェア |
RabbitMQ server 3.6.1
ユーザ : user01 パスワード : passwd |
OS | CentOS-7-x86_64-Minimal-1511.iso VirtualBox 5.0.16 |
RabbitMQ Server (vm-2nd)
ホスト名 | vm-2nd |
---|---|
IP | 192.168.10.120 |
ミドルウェア |
RabbitMQ server 3.6.1
ユーザ : user01 パスワード : passwd |
OS | CentOS-7-x86_64-Minimal-1511.iso VirtualBox 5.0.16 |
準備
Clustering と QueueMirroring の設定をするにあたりホスト名とその名前解決が必要になるようです。
( ! ) 注意 RabbitMQ のリソース
ホスト名を変更すると User や Queue などが消えてしまう?
Queue、Exchange、Binding、User などは、ホスト名に関連付けされて /var/lib/rabbitmq/mnesia ディレクトリ以下に保存されるようです。 (Mnesia という分散データベースが使われているようです。 実際、消えてはないのですが、変更前のホスト名で保存されているので アクセスできなくなってしまうようです。
それぞれ以下のように設定します。 ホスト名を設定した後は、一度、再起動しておきます。vm-1st
/etc/hostname
vm-1st/etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.10.110 vm-1st 192.168.10.120 vm-2nd
reboot
vm-2nd
/etc/hostname
vm-2nd/etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.10.110 vm-1st 192.168.10.120 vm-2nd
reboot
ホスト名を変更した場合
guest 以外のユーザが存在するか確認します。
rabbitmqadmin list users name tags +--------+---------------+ | name | tags | +--------+---------------+ | guest | administrator | +--------+---------------+
guest しか存在しない場合、恐らく User が消えてしまっていると思います。 ユーザとパスワードを "user01" と "passwd" にして再作成します。 リソースへのアクセス権限と Web の管理コンソールを使用するための Role も設定します。
rabbitmqctl add_user user01 passwd rabbitmqctl set_permissions -p / user01 ".*" ".*" ".*" rabbitmqctl set_user_tags user01 administrator
もう一度ユーザを確認します。
rabbitmqadmin list users name tags +--------+---------------+ | name | tags | +--------+---------------+ | guest | administrator | | user01 | administrator | +--------+---------------+
user01 が追加されていれば OK です。
ユーザ作成は、Main の ClusterNode だけで OK です。 Sub の ClusterNode は、Main の ClusterNode の情報がレプリケートされます。
Clustering の設定方法には、いくつかあるのですが、 今回は、ManagementPlugin の CommandLineTool を使おうと思います。 そのためには、Cluster に参加する Node は、同じ ErlangCookie を持っていないといけないようです。 (コマンド実行時の認証に必要なようです。 ErlangCookie に特別な制限は、なくアルファベットの文字列であればなんでもよいようです。
とりあえず、vm-1st と vm-2nd に適当な同じ ErlangCookie 文字列を設定します。 手順としては、 1. RabbitMQ サーバーを停止してから ErlangCookie を変更する。 2. ErlangCookie を変更した後、RabbitMQ サーバーを再起動する。 とします。
vm-1st と vm-2nd のターミナルからそれぞれ以下のコマンドを実行します。vm-1st
systemctl stop rabbitmq-server.service echo MYERLANGCOOKIESTR > /var/lib/rabbitmq/.erlang.cookie systemctl start rabbitmq-server.service
vm-2nd
systemctl stop rabbitmq-server.service echo MYERLANGCOOKIESTR > /var/lib/rabbitmq/.erlang.cookie systemctl start rabbitmq-server.service
ErlangCookie
vm-1st の ErlangCookie を vm-2nd にコピーするのもよいと思います。 vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
mv /var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie_bk scp root@vm-1st:/var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie chown rabbitmq /var/lib/rabbitmq/.erlang.cookie
準備は、これで終わりです。
Clustering の設定
今、vm-1st と vm-2nd の Clustering がどうなっているか確認してみましょう。
まず、vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqctl cluster_status Cluster status of node 'rabbit@vm-1st' ... [{nodes,[{disc,['rabbit@vm-1st']}]}, {running_nodes,['rabbit@vm-1st']}, {cluster_name,<<"rabbit@vm-1st">>}, ...
見てみると "rabbit@vm-1st" という Cluster があり このサーバー(vm-1st) だけが参加していて起動している。 こんな感じでしょうか?
次に、vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
rabbitmqctl cluster_status Cluster status of node 'rabbit@vm-2nd' ... [{nodes,[{disc,['rabbit@vm-2nd']}]}, {running_nodes,['rabbit@vm-2nd']}, {cluster_name,<<"rabbit@vm-2nd">>}, ...
こちらも "rabbit@vm-2nd" という Cluster があり このサーバー(vm-2nd) だけが参加していて起動している。
整理してみると vm-1st と vm-2nd のどちらも それぞれが Cluster を持っていて自サーバーのみ参加していて起動している。
Disk Node と RAM Node
Node の保存先に Disk と RAM があるようだ。 保存先に RAM を選ぶとパフォーマンスを稼げるということらしいが 一般的には、Disk を選択するのが無難なようだ。
ではでは、vm-2nd を vm-1st の Cluster に参加させてみましょう。 手順としては、 1. vm-2nd を offline にする。 2. vm-2nd を vm-1st の Cluster に参加させる。 3. vm-2nd を online にする。 のようになります。
vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
rabbitmqctl stop_app rabbitmqctl join_cluster rabbit@vm-1st rabbitmqctl start_app
ちゃんと Cluster に参加できたか確認します。 vm-1st と vm-2nd の両方のターミナルで以下のコマンドを実行します。vm-1st
rabbitmqctl cluster_status Cluster status of node 'rabbit@vm-1st' ... [{nodes,[{disc,['rabbit@vm-1st','rabbit@vm-2nd']}]}, {running_nodes,['rabbit@vm-2nd','rabbit@vm-1st']}, {cluster_name,<<"rabbit@vm-1st">>}, ...
vm-2nd
rabbitmqctl cluster_status [{nodes,[{disc,['rabbit@vm-1st','rabbit@vm-2nd']}]}, {running_nodes,['rabbit@vm-1st','rabbit@vm-2nd']}, {cluster_name,<<"rabbit@vm-1st">>}, ...
どうなったでしょう? ちょっと見てみると vm-1st と vm-2nd のどちらにも "rabbit@vm-1st" という Cluster があり そこにどちらも参加していてどちらも起動している。 いけてそうな感じですね!
Clustering の解除
vm-2nd を vm-1st の Cluster から解除する手順としては、 1. vm-2nd を offline にする。 2. vm-2nd を vm-1st の Cluster から解除する。 3. vm-2nd を online にする。 のようになります。
vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl start_app
RabbitMQ と Erlang の version
RabbitMQ の version が違う Node は、Cluster に参加できないようです。 同様に Erlang の version が違う Node も Cluster に参加できないようです。 Clustering された RabbitMQ の version up は、なかなか大変?!
Queue を宣言する
とりあえず、Queue がないと始まらないので宣言をします。 Queue名を "myQueue" とします。 vm-1st と vm-2nd のどちらでも構わないのですが ここでは、vm-2nd を HomeNode にして Queue を宣言したいと思います。
vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
rabbitmqadmin declare queue name=myQueue
宣言できたか確認します。 デフォルト(無名)の Exchange に Bind されているかも確認します。vm-2nd
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 0 | +---------+---------+---------------+----------+
Queue名が "myQueue"、durable が "True"、HomeNode が "vm-2nd" で宣言されているようです。
rabbitmqadmin list bindings +------------+-------------+-------------+ | source | destination | routing_key | +------------+-------------+-------------+ | | myQueue | myQueue | +------------+-------------+-------------+
Queue名を RoutingKey にしてデフォルトの Exchange に Bind されているようです。
Queue の HomeNode
デフォルトでは、Queue を宣言した Node が HomeNode になるようです。
以下で HomeNode の決定条件を変更できるようです。 ・Queue 作成時の x-queue-master-locator 引数 ・queue-master-locator policy ・configuration ファイル
HomeNode の決定条件として以下の値が設定可能なようです。 ・min-masters ・client-local (デフォルト) ・random
Queue の削除
Queue を指定して以下のコマンドを実行します。
rabbitmqadmin delete queue name=myQueue
HomeNode でない vm-1st からはどう見えているか確認したいと思います。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 0 | +---------+---------+---------------+----------+
vm-2nd と同じように見えているのが分かると思います。
HomeNode でない vm-1st からメッセージを送信するとどうなるか試してみたいと思います。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqadmin publish routing_key=myQueue payload="Hello?"
送信されたメッセージが Queue に届いたか確認します。 vm-1st と vm-2nd の両方のターミナルで以下のコマンドを実行します。vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 1 | +---------+---------+---------------+----------+
vm-2nd
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 1 | +---------+---------+---------------+----------+
ちゃんと届いているようです。
Queue には、HomeNode があり Master / Slave のような構成になっているが どこの Node からメッセージを送信しても Cluster 内のすべての Queue にレプリケートされるようです。 ある意味、MultiMaster的な動きになっていると思います。
メッセージの取り出しと削除
メッセージを取り出す場合は、Queue を指定して以下のコマンドを実行します。
rabbitmqadmin get queue=myQueue requeue=false
requeue を省略、または "true" にすると取り出したメッセージがもう一度、Queue に戻されます。
メッセージを削除する場合は、Queue を指定して以下のコマンドを実行します。
rabbitmqadmin purge queue name=myQueue
QueueMirroring していない状態で HomeNode になっている vm-2nd が落ちるとどうなるか試してみます。 vm-2nd の RabbitMQ サーバーを停止させた後、vm-1st から Queue がどう見えているか確認します。
vm-1st と vm-2nd のターミナルでそれぞれ以下のコマンドを実行します。vm-2nd
systemctl stop rabbitmq-server.service
vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | | +---------+---------+---------------+----------+
Queue は見えているもののメッセージが見えなくなりました。
vm-2nd が復帰した時、メッセージがどうなっているか確認したいと思います。 vm-2nd の RabbitMQ サーバーを起動させた後、vm-1st と vm-2nd から Queue が どう見えているか確認します。
vm-1st と vm-2nd のターミナルでそれぞれ以下のコマンドを実行します。vm-2nd
systemctl start rabbitmq-server.service
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 0 | +---------+---------+---------------+----------+
vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 0 | +---------+---------+---------------+----------+
メッセージが消えてしまっているのが分かると思います。
HomeNode が落ちてもメッセージが消えないように QueueMirroring の設定をすることにします。
DeliveryMode
メッセージ送信時に DeliveryMode を persistent にすると 送信したメッセージがディスク領域に保存されるようになるため HomeNode が落ちてもメッセージが消えてしまうことはないようです。 ただし HomeNode が落ちている間、メッセージが見えなくなるので取り出しはできないようです。
DeliveryMode を persistent にしてメッセージを送信する。
rabbitmqadmin publish routing_key=myQueue payload="Hello?" properties='{"delivery_mode":2}'
HomeNode を停止する。
systemctl stop rabbitmq-server.service
メッセージを取り出してみる。
rabbitmqadmin get queue=myQueue requeue=false *** Not found: /api/queues/%2F/myQueue/get
Not found になりメッセージを取り出せない。
HomeNode を起動させて Queue を確認する。
systemctl start rabbitmq-server.service
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 1 | +---------+---------+---------------+----------+
メッセージは、残っている。 メッセージを取り出してみる。
rabbitmqadmin get queue=myQueue requeue=false +-------------+----------+---------------+---------+---------------+------------------+-------------+ | routing_key | exchange | message_count | payload | payload_bytes | payload_encoding | redelivered | +-------------+----------+---------------+---------+---------------+------------------+-------------+ | myQueue | | 0 | Hello? | 6 | string | False | +-------------+----------+---------------+---------+---------------+------------------+-------------+
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-2nd | 0 | +---------+---------+---------------+----------+
メッセージが取り出せた。
QueueMirroring の設定
Clustering の設定と Queue の宣言に続いて QueueMirroring の設定に入りたいと思います。 policy を使って設定することになるようです。 設定する policy は、ha-mode とその値の ha-params になるようです。
先ほど宣言した Queue を Cluster 内のすべての Node で Mirroring するようにします。 policy名を "myPolicy" とします。
vm-1st と vm-2nd のどちらでも構わないのですが とりあえず、vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqctl set_policy myPolicy "^myQueue$" '{"ha-mode":"all"}'
rabbitmqctl set_policy コマンドの書式
rabbitmqctl set_policy policy名 パターン policy設定
-
- policy名
-
policy名を指定する。
- パターン
-
policy を適用する Queue を正規表現を使って指定する。
- policy設定
-
設定する policy をJSON 形式で指定する。
ha-mode と ha-params
ha-mode は、3つあり ha-params と合わせて以下のように設定できるようです。
-
- all
-
すべての Node で mirroring する。(ha-params はない)
例) '{"ha-mode":"all"}'
- exactly
-
指定した数の Node で mirroring する。
ha-params には、Node数を指定する。
例) '{"ha-mode":"exactly","ha-params":2}'
- nodes
-
指定した Node で mirroring する。
ha-params には、Node名を指定する。
例) '{"ha-mode":"nodes","ha-params":["rabbit@vm-1st", "rabbit@vm-2nd"]}'
policy が作成できたか確認します。 vm-1st のターミナルから以下のコマンドを実行します。
rabbitmqctl list_policies Listing policies ... / myPolicy all ^myQueue$ {"ha-mode":"all"} 0
作成できているようです。
policy の削除
policy を指定して以下のコマンドを実行します。
rabbitmqctl clear_policy myPolicy
Cluster 内の Node で QueueMirroring が同期しているか確認します。 vm-1st のターミナルから以下のコマンドを実行します。
rabbitmqctl list_queues name slave_pids synchronised_slave_pids Listing queues ... myQueue [<rabbit@vm-1st.3.24159.1>] [<rabbit@vm-1st.3.24159.1>]
"myQueue" の slave に vm-1st があり、同期している slave にも vm-1st があるようなので 問題ないと思います。
では、メッセージを送信してみて HomeNode が落ちてもメッセージが消えてしまわないか確認したいと思います。
vm-1st からメッセージを送信します。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqadmin publish routing_key=myQueue payload="Hello?"
HomeNode になっている vm-2nd の RabbitMQ サーバーを停止します。 vm-2nd のターミナルから以下のコマンドを実行します。vm-2nd
systemctl stop rabbitmq-server.service
この状態で HomeNode でない vm-1st で Queue がどういう状態になっているか確認します。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-1st | 1 | +---------+---------+---------------+----------+
メッセージが消えずに残っているのが確認できると思います。 もう一点、HomeNode が vm-2nd から vm-1st に変わっているのも確認できると思います。
ここで vm-2nd の RabbitMQ サーバーが起動すると vm-1st と vm-2nd から Queue がどう見えているか確認します。
vm-1st と vm-2nd のターミナルでそれぞれ以下のコマンドを実行します。vm-2nd
systemctl start rabbitmq-server.service
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-1st | 1 | +---------+---------+---------------+----------+
vm-1st
rabbitmqadmin list queues name durable node messages +---------+---------+---------------+----------+ | name | durable | node | messages | +---------+---------+---------------+----------+ | myQueue | True | rabbit@vm-1st | 1 | +---------+---------+---------------+----------+
HomeNode が vm-1st に変更されたままになっているのが確認できると思います。 HomeNode が落ちたタイミングで別の Node が HomeNode になり メッセージを引き継ぐ形になっているようです。 そのため HomeNode が落ちてもメッセージが消えることは、ないようです。
HomeNode が変更されたので QueueMirroring の同期を確認します。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqctl list_queues name slave_pids synchronised_slave_pids Listing queues ... myQueue [<rabbit@vm-2nd.3.293.0>] []
slave として、vm-2nd が追加されているのは、確認できますが同期がうまくいっていないようです。
vm-1st のターミナルから以下のコマンドを実行して同期の設定をします。vm-1st
rabbitmqctl sync_queue myQueue
もう一度、QueueMirroring の同期を確認します。 vm-1st のターミナルから以下のコマンドを実行します。vm-1st
rabbitmqctl list_queues name slave_pids synchronised_slave_pids Listing queues ... myQueue [<rabbit@vm-2nd.3.293.0>] [<rabbit@vm-2nd.3.293.0>]
今度は、問題なく同期できているのが確認できると思います。
QueueMirroring の同期
デフォルトの QueueMirroring の同期設定は、手動になっているようです。 policy 作成時に ha-sync-mode を "automatic" にすると自動同期にすることができるようです。
rabbitmqctl set_policy myPolicy "^myQueue$" '{"ha-mode":"all", "ha-sync-mode":"automatic"}'
おわりに
なかなかいい感じに MultiMaster のような形で冗長化できたのではと思います。 後は、Cluster 内の Node に仮想の IP を設定して、アプリからアクセスする感じでしょうか?