gRPC, MQ, NewtMQ, RabbitMQ, Ruby, STOMP, ZeroMQ, ベンチマーク

 こんにちは、インフラ統括本部の大山 裕泰です。
 NewtMQ v0.2 をリリースしました。

https://github.com/newtmq/newtmq-server/releases/tag/v0.2.0

 v0.2 では temp-queue を実装し Brokered MQ を利用した高速な RPC 処理が実現できるようになりました。
 今回は RabbitMQ(AMQP) 及び RabbitMQ(STOMP) に加えて Broker-less MQ の ZeroMQ、Google 謹製の RPC フレームワーク gRPC、そして NewtMQ で RPC のベンチマークを取ってみました。
 尚、御馴染みの Apache Kafka には 同様の機能がない模様 なため、検証しておりません。

temp-queue とは?

 NewtMQ (v0.2) に実装した temp-queue の仕組みは、以前のツチノコブログエントリ で紹介した rabbitmq-stomp プラグインのものと同じ仕組みになります。RabbitMQ 本体にも 同様の機能 が実装されています。
 簡単に説明すると pub-sub モデルにおいては Publisher と Subscriber はそれぞれお互いの存在を認知しませんが、Subscriber が、受け取ったメッセージを送ったある Publisher に対してメッセージを返信したいケースにおいて、当該 Publisher 専用の一時キューに対してメッセージを送ることで Subscriber と特定 Publisher 間でメッセージパッシングを行えるようにする仕組みになります。

 MQ を介して RPC を行う際、この仕組みが有効に作用します。

RPC over MQ

 RPC はネットワークを介した計算機資源を活用する分散システムの世界で 30 年以上の歴史がある枯れた技術です。
 分散システムにおいてはネットワークや計算機に障害が発生した際においてもサービスが安定して稼働できること、そして計算機資源の動的な増減に対応できることが古くからの課題として知られています。これらの問題を解決する手段の一つとして MessageQueue が広く利用されてきました。
 大規模化・複雑化した今日のシステムの多くはこうした仕組みに支えられています。数千 ~ 数万のサーバから構成される OpenStack では、RabbitMQ を用いた RPC を行うアーキテクチャ を採っています。

Broker-less MQ

 ここで、今回の比較する ZeroMQ に代表される Broker-less MQ と呼ばれる MQ について紹介します。
 ’MQ’ という名前を冠している点で RabbitMQ や NewtMQ と同じく、Queuing や Pub/Sub といったシステムモデルの実装をサポートする機能を提供している点で共通ですが、メッセージを仲介する ‘Broker’ を持たない点で異なります。
 RabbitMQ や NewtMQ の場合、以下のように Pub/Sub モデルにおいてクライアント (Publisher 及び Subscriber) は Broker に対してメッセージの送信/取得を行います。Apache Kafka や MQTT の実装として知られる ActiveMQ Apollo もこちらに分類されます。
   

 対して ZeroMQ の場合、以下のように Publisher が直接 Subscriber プロセスに対してメッセージを送信します。

 Broker を持た無い MQ システムでは、Broker を介した MQ と比較していくつかのメリットがあります。まずクライアントと Broker 間の通信が無くなるため、通信に伴うトラフィック量を減らせる事ができます。また ‘Broker’ で行う処理を省くため高速なメッセージパッシングを行うことができます。

ZeroMQ

 ZeroMQ の用途は Queuing や Pub/Sub モデルの実装に止まりません。ここで ZeroMQ 自体について補足で紹介します。
 ZeroMQ では ‘ZeroMQ Socket’ と呼ばれる TCP Scoket を抽象化したオブジェクトに対して、1対1, 1対N, N対1, N対N の通信を実現する機能 を加えています。更に、これらの通信のベースに Queuing の仕組みを設ける事で通信の可用性を高め、従来の TCP Socket を用いた通信を行うアプリケーションに対して、高い機能性と可用性を備えた仕組みを提供しています。
 更に ZeroMQ の Router Socket を利用することで、メッセージの配送を仲介する Proxy をつくることができ Broker MQ のような振る舞いをさせることも出来ます。

測ってみた

 それではここで MQ を利用した RPC 処理のベンチマークを取ってみます。ベンチーマークは Broker MQ の雄 RabbitMQ(AMQP, STOMP) Broker-less MQ の代表格 ZeroMQ、そして RPC フレームワークとして現在非常に活発に開発が行われている Google 謹製の RPC フレームワーク gRPC、そして NewtMQ に対して行います。ベンチマークでは以下の RPC ベンチマークツールを利用します。

* https://github.com/userlocalhost2000/rpc-bench

 各クライアントライブラリはそれぞれ以下を使用しています。

ターゲット ライブラリ (バージョン)
RabbitMQ(AMQP) bunny (2.3.1)
RabbitMQ(STOMP) stomp (1.4.0)
NewtMQ stomp (1.4.0)
gRPC grpc (0.14.1)
grpc-tools (0.14.1)
ZeroMQ libzmq (4.2.0)
ffi-rzmq (2.0.4)
ffi-rzmq-core (1.0.5)

 今回は、各手法の実行時間の比較が目的のため、クライアント、サーバ、及び MQ を全て同一ホスト上で実行しています。実行ホスト環境は以下のとおりです。

CPU Intel Core i7-6700 CPU @ 3.40GHz
RAM 32GB
OS Ubuntu14.04
Ruby 2.2.2
RabbitMQ 3.6.1-1

 RPCBench では、クライアントがサーバに対して数値を送り、サーバが送られた数値に 1 を足して結果を返し、最終的にすべての結果が送った数値 + 1 になっているかを検査するまでに要した時間を、リクエスト数、同時実行数のパラメータを変えて取得できます。
 
 まず同時実行数を 1 に固定し、リクエスト数を 32k ~ 512k にそれぞれ設定した結果を以下に示します。

 予想通り ZeroMQ が最も早いですが NewtMQ もほぼ同じ程度の速度で RPC をさばけています。予想以上のスピードに我ながら驚いています(結果の取り違いを疑いました)。
 それぞれのベンチマークにおいて RPC サーバ処理 (数値を受け取って +1 する処理) は共通ですが、クライアントライブラリがそれぞれ異なるため、純粋に MQ の違いについての比較にはなりません。
 ただし RabbitMQ(STOMP) との比較に関しては、サーバ処理もクライアントライブラリもそれぞれ同じものを用いており、MQ だけが違うので純粋な NewtMQ と RabbitMQ(STOMP) の性能の違いがこの結果かわかります。

 続いてリクエスト数を 10k に固定し、同時実行数を 4 ~ 64 にそれぞれ設定した結果を以下に示します。

 

 やや ZeroMQ との差が開きましたが Broker MQ においては NewtMQ が最も速度が出ていることがわかります。

おわりに

 NewtMQ に temp-queue を実装したことで、RabbitMQ よりも高速に RPC を処理できることを確認しました。
 しかし NewtMQ はその他の機能面や信頼性、実績において RabbitMQ に遠く及ばないので、メジャーバージョンがリリースできるくらいまでは暖かい目で見守っていただければと思います。

 性能改善の余地はまだ残されているので、NewtMQ はまだまだ速くなると思っています。

RabbitMQ, Ruby, STOMP

はじめに

 先日 CodeZine に寄稿した記事 で簡単に触れた STOMP について紹介します。
 後ほど解説しますが、STOMP は非常にシンプルなプロトコルで読もうと思えば数時間で読めてしまいます。
 今回は STOMP プロトコルがどんなものかという事と、STOMP の実装として RabbitMQ の STOMP アダプタ (以下、’rabbitmq-stomp’) に注目し、どういう事ができて AMQP と比べてどうなのかということを簡単に示したいと思います。

STOMP とは?

 STOMP は非常にシンプルで拡張可能なメッセージパッシングプロトコルになります。STOMP は AMQP や MQTT などのバイナリプロトコルと異なり、テキストベースのプロトコルになります。
 どういうことかというと、STOMP ではフレームという単位でメッセージ (content) のやり取りを行います。キューにメッセージを送る際には SEND フレームを STOMP サーバに対して送り、キューに格納されているメッセージを取得する際にはサーバに対して SUBSCRIBE フレームを送信し、メッセージ本体が格納された MESSAGE フレームの送信をサーバから待ち受けるというように、フレームを単位として処理が行われます。そして STOMP は一般にテキストベースのプロトコルと呼ばれる、フレームのヘッダー及びメッセージ本体をテキストデータとしてネットワークに流します。これに対して AMQP や MQTT では、ネットワークに流すデータを圧縮するために、こうしたデータ (AMQP の場合フレーム (Frame)、MQTT ではコマンドメッセージとそれぞれ呼ばれている) をバイナリにエンコードした上でネットワークに流しています。
 こうしたテキストベースのプロトコルでは、実装が非常に簡単に行える利点があります。例えば、クライアント側の処理であれば、telnet からメッセージを送信するなんて事も出来てしまいます。
 以下では、telnet から STOMP サーバに対してメッセージ ‘hogefuga’ を送信し、その後そのメッセージを取り出すという処理を行っています。

 (太文字の部分が STOMP サーバから送られた内容になります)

 更にクライアントがサーバからメッセージを取得した際に送る確認応答 (ACK) を TCP などのように後から累積して返す方法 (累積応答確認) が選択でき、短時間に大量のメッセージを受信することができます。
 しかし AMQP 0.9.1 にあるような fanout や topic exchange による複数のキューに対するメッセージのブロードキャストや、柔軟なメッセージルーティングを実現させることが出来ません(ただ rabbitmq-stomp では拡張機能として topic 機能によるメッセージブロードキャストをサポートしています)。またトランザクション処理について、STOMP ではサポートされていますが AMQP のようにシステムが自動でトランザクション処理をしてくれるのに対して、ユーザがトランザクション (複数のフレームから成る処理のまとまり) を規定してやらなければなりません。
 このように STOMP ではリッチな機能やトランザクション処理の抽象化が行われている AMQP と比べて非常に簡素なプロトコルで、特定のプロセスとメッセージパッシングを行う上でプリミティブな機能を提供しています。

STOMP 実装について

 STOMP の実装は いろいろ ありますが、人気が無いためか最新の STOMP v1.2 に対応し且つキチンとメンテされているプロジェクトは ActiveMQ Artemisrabbitmq-stomp くらいです。今回は rabbitmq-stomp に注目して特徴を解説します。
 rabbitmq-stomp のセットアップの方法については 公式マニュアル をご参照ください。

使い方

 以下は Ruby の STOMP パッケージ stomp を用いて、キュー ‘hoge’ に対してメッセージを送信するサンプルプログラムになります。

送信処理(sender.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.publish "/queue/fuga", "hello world!"
受信処理(receiver.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.subscribe '/queue/fuga'
loop { puts conn.receive.body }

 簡単ですね。stomp パッケージでは Stomp::Client クラスの publish, subscribe の第一引数で指定しる文字列がそれぞれ SEND フレームと SUBSCRIBE フレームの destination ヘッダで指定されます。先頭の ‘/queue/’ は STOMP のキューを表す予約語で、その後にキュー名 ‘hoge’ を指定しています。
 rabbitmq-stomp ではこうした STOMP v1.2 で規定されている機能に加えて、STOMP クライアントから AMQP のキューに対して送る機能やメッセージを broadcast する機能拡張を行っています。以下では、rabbitmq-stomp の拡張機能について簡単なサンプルと共に紹介してゆきます。

/topic/xxx

 送信キュー名 (SEND フレームの destination ヘッダで指定されてる値) のプレフィックスが ‘/topic’ の場合、rabbitmq-stomp ブローカーは AMQP の topic exchange(*1) の要領でメッセージを STOMP キューに格納します。
 STOMP v1.2 の仕様では destination ヘッダのセマンティクスは規定されていませんが、rabbitmq-stomp ではこうした独自のセマンティクスを持たせる事で、様々なオリジナル機能を提供しています。逆に rabbitmq-stomp で規定されていない値を SEND や SUBSCRIBE フレームの destination ヘッダで指定した場合、ERROR が帰ってきます。

送信処理(sender.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.publish("/topic/fuga", "hello world!")
受信処理(receiver.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.subscribe '/topic/fuga'
loop { puts conn.receive.body }

(*1) AMQP にどのような exchange があって、それぞれどのような働きをするかについては こちらの記事 が詳しいです。

/exchange/(exchange-name)/xxx

 destination ヘッダーの冒頭に予約語 ‘/exchange’ が指定された場合、それに続く (exchange-name) で指定される RabbitMQ の exchange によってメッセージの転送を実施することができます。RabbitMQ によって提供されている exchange は ‘rabbitmqctl list_exchanges’ コマンドによって取得できます。以下ではメッセージのブロードキャストを行う fanout exchange を使って、メッセージを送受信する処理を記述しています。

送信処理(sender.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.publish("/exchange/amq.fanout/foo", "hello world!")
受信処理(receiver.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.subscribe '/exchange/amq.fanout/foo'
loop { puts conn.receive.body }

/amq/xxx

 ’/amq’ が destination で指定された場合、AMQP 側(RabbitMQ 本体)の処理で作成されたキューに対してメッセージを送ることができます。これによって STOMP クライアントから AMQP のキューに対してメッセージが送れるわけですが、用途は謎です。RabbitMQ 本体で作成した AMQP の名前付きキュー ‘amq-test’ に対して STOMP クライアントからメッセージを送受信する処理を以下に示します。

送信処理(sender.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.publish("/amq/queue/amq-test", "hello world!")
受信処理(receiver.rb)
require 'bunny'

c = Bunny.new(:host => 'localhost', :user => 'guest', :password => 'guest')
c.start

ch = c.create_channel
q = ch.queue('amq-test')

begin
  q.subscribe(:block => true) do |_, _, data|
    puts data
  end
rescue Exception => _
  ch.close
  c.close
end

/temp-queue/xxx

 最後に ‘/temp-queue’ が指定された場合について紹介します。これまでの機能は全て ‘destination’ ヘッダに対して指定する予約語でしたが、これは SEND フレームの ‘reply-to’ ヘッダに記述します。SEND フレームの ‘reply-to’ に ‘/temp-queue’ を指定すると、rabbitmq-stomp はランダムな名前が設定された一時キューを作成し、メッセージを受信する側が受け取る MESSAGE フレームの reply-to ヘッダに一時キューの名前を格納します。MESSAGE フレームを受け取った側は ‘reply-to’ ヘッダに指定されたキューに対してメッセージを送信すると、最初に SEND フレームを送った側において ‘reply-to’ ヘッダで指定したラベルから、メッセージを受け取ることができます。図にすると以下のようになります。

 この仕組みは、特定の相手からのメッセージの返信があるケースで便利です。特に RPC などにおいて効果を発揮します。また client から送った最初の SEND フレームの ‘reply-to’ ヘッダで指定する受信用のキュー名はセッション毎に独立しており、別のプロセスが同名の ‘/temp-queue’ を指定したとしても、別のプロセス宛の返信メッセージが混在することはありません。最後にサンプルプログラムを示します。

送信処理(sender.rb)
require 'stomp'

conn = Stomp::Connection.open
conn.publish("/queue/test-fuga", "hello world!", {
  'reply-to' => '/temp-queue/test'
})
puts conn.receive.body
受信処理(receiver.rb)
conn = Stomp::Connection.open
conn.subscribe '/queue/test-fuga'
loop do
  msg = conn.receive

  puts "received: #{msg.body}"

  conn.publish(msg.headers['reply-to'], '(reply) ' + msg.body)
end

AMQP との比較

 ここまでの話で STOMP がどんなもので、STOMP の実装の一つ rabbitmq-stomp でどんなことが出来るかについて紹介しました。最後に RabbitMQ の AMQP 実装と STOMP 実装での簡単なパフォーマンスベンチマークを取った結果を紹介したいと思います。ベンチマークで用いたスクリプトは こちら になります。またベンチマークの実行環境は以下のとおりです。

 ベンチマークでは、複数のサイズのメッセージを大量に送受信し、処理が完了するまでの時間でスループットの傾向を調べています。以下が実行結果になります。

 横軸が送信した各メッセージサイズを表し、縦軸が処理を終えるまでの時間になります。また棒グラフと折れ線グラフはそれぞれ、送信処理と受信処理を表しています。
 メッセージストアは STOMP の方が高速ですが、メッセージの取得処理は AMQP の方が 2 ~ 3 割ほど早いです。恐らく、rabbitmq-stomp において何がしかの実装上のオーバーヘッドがかかっていることが予想されます。

最後に

 rabbitmq-stomp の Contributor に名前を連ねました☆(ゝω・)vキャピ

RabbitMQ, Ruby

 こんにちは、インフラ統括本部の大山裕泰です。

 CodeZine に RabbitMQ ネタの記事 を寄稿致しました。
こちらは、本稿執筆時点での最新メジャーバージョンで組み込まれた Lazy Queues についての記事になります。

 要点をまとめると

* Apache Kafka のようなストレージストアのキューイングが選択できるようになった。

* 消費メモリが劇的に減りバッチシステムなどから定期的に取り出す処理など、
 キューに大容量のデータが滞在するようなケースで利用できるようになった。
 また、キューイングのスループットが安定するようになった。

* Lazy Queues の使い方についてコードを用いて紹介。

 また Lazy Queues は 時期メジャーバージョン (3.7.0) でデフォルトのキュー設定に切り替える といった議論が進められており、
 RabbitMQ を使っている方には益がある内容だと思いますので、是非ご一読いただければと思います。

Ruby, プログラミング

はじめに

 こんにちは、インフラ統括本部の大山裕泰です。今回は、EventMachine によってイベント駆動型なサーバを記述した場合と、従来のフロー駆動型なサーバとで、高負荷環境にいおいてどれだけリソースの使い方に違いがあるのかを調べてみたという話になります。
 すっごい今更な内容ですが、この辺りの内容をキチンと押さえておくと、サーバサイドアプリケーションを記述する際、どのようにサーバを記述すれば(高負荷になった際に)どのマシンリソースが、どのように使われるかがわかるようになって嬉しいと思います。

EventMachine って何?

 I/O 多重化によるイベント駆動 I/O (Event Driven I/O) を実現するためのミドルウェアで Rails などで利用されています。RabbitMQ クライアントの一部 でも使われています。

イベント駆動 I/O って何?

 イベント駆動型プログラミング (Event Driven Programming) と呼ばれるプログラミングパラダイムを用いて I/O 処理を実現したものになります。 イベント駆動型プログラミングは、決められた手続きに沿って順に処理を行うフロー駆動型プログラミングの対抗概念になり、GUI プログラミングのようにユーザからのキー入力やマウス操作を “イベント” という形に抽象化し、これらを処理するコールバックルーチン “イベントハンドラ” をそれぞれ記述してゆきます。
 フロー駆動型プログラムにおける I/O 処理では、ソケットファイルディスクリプタを通してバッファからデータを取り出し、何か処理して、またデータを取り出し… というような感じで、データストリームに対して処理を行ってきました。例によって Echo サーバをフロー駆動な感じで記述すると以下のようになります。

 コネクションの確立、データストリーミング処理、切断処理といった一連の処理が一つの手続きによって記述されているのがわかります。
 これに対して、イベント駆動型プログラムにおける I/O 処理では、こうしたデータストリームをイベントという形に変換してやり、これをユーザが定義したイベントハンドラに処理をさせます。
 EventMachine によって Echo サーバをイベント駆動な感じで記述すると以下のようになります。

 コネクションの確立、データの取得、切断処理がそれぞれイベントハンドラで記述できています。

何が嬉しいの?

 イベント駆動 I/O についてグダグダと書いてきましたが、EventMachine によって得られるもう一つの恩恵 (というか今回はこっちがメイン) として I/O を多重化してくれます。I/O 多重化 (I/O Multiplexing) とは、複数の I/O ストリーム (ファイルディスクリプタ) を一元的に操作する手法で、select / poll / ppoll 等のシステムコールによって実現されます。つまり、一つのプロセス (あるいはスレッド) から複数のファイルディスクリプタを同時に取り扱うことができるようになります。これに対して、先ほどのフロー駆動 I/O による Echo サーバの例では、コネクションの度にスレッドを生成し、ソケットファイルディスクリプタはそれぞれのスレッドが独立して持っています。

 では何故 I/O 多重化なのでしょうか? それは C10K 問題 (C10K Problem) を解消する手法として期待されたためです (C10K 問題については、丁寧に解説してくれているサイトがいくつもあるので、併せて参照してください) 。
 ざっくりと説明すると、凄まじい数のコネクションを受け付けるサーバにおいて、フロー駆動 I/O の例で示したような実装をするとリソースを大量消費してしまい、処理が捌けなくなるよーという内容です。
 どういうことかというと、コネクション毎にプロセス (ないしはスレッド) を生成して、ビジネスロジックを走らせ、レスポンスを返すやり方だと、プロセスを生成する場合、プロセス毎にデータセグメントがコピーされるので大量にメモリが消費され、また大量に発生するコンテキストスイッチによる大量の TLB フラッシュによって大量の CPU 時間をにカーネルに持って行かれてしまいます。スレッドを生成する場合でも、プロセスのスタック領域のコピーや thread_info などカーネル内部に確保されるスレッド用のデータによって線形にメモリが消費され、またプロセスの場合ほどではないにしろ大量にコンテキストスイッチが発生するためコネクションが増えるに従って CPU 時間をロスします。
 多重 I/O では、単一スレッドのプロセスにおいて、複数のファイルディスクリプタを扱うことができるため、上記の問題が回避できます。
 尚、I/O 多重化をイベント駆動プログラミングと絡めて話していますが、イベント駆動 I/O を実現する上で I/O を多重化すると嬉しいことがあるので、たまたま同時に語られることが多いですが、これらはそれぞれ独立した概念で、I/O 多重化はイベント駆動でなければならないということではないです。

検証

 ここで、先ほど紹介しましたフロー駆動で実装した Echo サーバ (normal-echo) と、イベント駆動で実装した Echo サーバに (event-echo) 対してベンチマークを実施し、それぞれのリソースの使い方について確認します。尚、ベンチマークツールには methane さんの echoserver の client を用いました。測定したサーバ環境は以下のとおりです。

* CPU : Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
* RAM : 4GB
* OS : Ubuntu 14.04
* Ruby : 2.0.0p384

 以下のように “同時実行数” と “スレッド当たりのコネクション数” をそれぞれ 100 から 400 までの条件で実行し、サーバのメモリ消費量を計測しました。

 実行結果は以下のとおりです。緑の棒グラフで示したものが normal-echo におけるメモリ消費量を表し、青色が event-echo におけるメモリ消費量を表しています。

 normal-echo の方はコネクション数が増えるに従ってメモリ消費量が増えているのに対して、EventMachine で実装した方はほぼ一定値で推移しています。
 また、スループット (処理したリクエスト数の秒平均値) を以下に示します。例によって、normal-echo の結果を緑、event-echo の結果を青で示しています。


 
 今回はまたま全ての結果が EventMachine で実装した方が優れていますが、あらゆるケースにおいて EventMachine が優れているわけではありません。
 normal-echo における各 CPU コアの利用率を表したものを以下に示します。

 全てのコアがまんべんなく利用できているのがわかります。これに対して event-echo における各 CPU コアの利用率を表したものが以下になります。


 
 特定のコアに処理が集中しているのがわかります。というのも、I/O 多重化によって一つのスレッドで全ての処理を捌いているので、マルチコアの恩恵が受けられていないためにこのようになっています。
 なので、先ほど示したスループットの結果は実行環境やワークロードによって変わってきます。今回利用したベンチマークツールでは、1 コネクション当り、6 byte のショートメッセージ “hello\n” をデフォルトで 100 回送るというもので、サーバも Echo サーバなのでコンテキストスイッチの CPU コストよりも Echo サーバのロジックが軽量だったため、上記のようになったと思われます。

終わりに

 今回は、イベント駆動プログラミング、I/O 多重化、そして I/O 多重化によって、どのリソースがどのように消費されるかについて見てきました。
 ここでの内容によって、効果的にリソースを消費するサーバ実装の手助けになれば幸いです。

RabbitMQ, Ruby

 こんにちは、若手ツチノコの大山裕泰です。Ruby Advent Calendar 2015 の 12 日目を担当しまーす。

はじめに

 複数のホストに跨った処理を実現するシステムを構築する際、何がしかの Message Queue (MQ) システムが利用されるケースがあります。MQ を利用することで、送信元からは接続先についての様々な事(どういうネットワークの先に、どれだけ居て、どんな事をやっているか)について考えなくてよくなるため、複雑な分散システムをシンプルに設計・実装することができます。
 MQ の中でも特に AMQP は、柔軟なメッセージルーティングと確実なメッセージ転送機能に加え、高いスケーラビリティを実現するシステムとして古くから利用されています。AMQP については、GREE のエンジニアブログ で分かりやすく解説されています。
 AMQP の実装としては ActiveMQRabbitMQ が有名です。特に RabbitMQ は OpenStack や VMware NSX, Sensu など分散システムのミドルウェアとして様々なソフトウェアで利用されています。
 もちろん RabbitMQ が万能というわけではなりません。メッセージの永続化による確実なメッセージ転送などの機能とパフォーマンスはトレードオフになっています。特にリアルタイムシステムなどには向きません。
 これに対して AMQP で規定されている柔軟なメッセージ転送機能を実現する Broker 機能を一部 (ないしは全部) 省いた Apache KafkaZeroMQ など、低いレイテンシで高いスループットを実現する実装があります。

概要

 今日は RabbitMQ による柔軟なメッセージトポロジーの構築を実現する機能について紹介します。

環境構築

 本題に入る前に RabbitMQ の環境構築作業について簡単に説明します。RabbitMQ のインストールは、本家のマニュアル (*1) や、いろんな人のブログで紹介されているのでそちらをご確認ください。
 インストールができたら、以下のコマンドでユーザ “ohyama” の作成と権限の設定を実施します。

 本編では MyMQ ライブラリ を使うのでこれの設定もしておきます。

本題

 以下の図は、RabbitMQ による一般的な Pub/Sub メッセージングモデルを表しています。

 Exchange (Broker) が Publisher からのメッセージを受け取り、Subscriber がキューを通してメッセージを取得します。
 これを MyMQ ライブラリを使うと、以下のようにメッセージの送受信が行えます。

Sub

Pub

 Publisher / Subscriber 双方で指定されている ‘e1’ と ‘r1’ はそれぞれ、AMQP の Exchange Name と Routing Key を表しています。
 MyMQ.receive を実施した後、別のターミナルから MyMQ.send を送るとメッセージ ‘data’ が送られることが確認できると思います。

 さて、ここで本題の exchange to exchange binding (e2e binding) について紹介します。尚、元ネタは こちら になります。
 通常、AMQP における Routing Key は、Exchange と Queue とを関連付けるために存在しています。この仕組みによって、キューの存在が Publisher 側から隠蔽されます。Publisher は Broker (Exchange) にメッセージを送ることだけを考えればよく、Publisher がどこに居て、どれだけの数存在するかは全く気にしなくてよくなります。後のことは Broker がよしなにしてくれます。
 e2e binding では同様に、ある Exchange と別の Exchange とを関連づけ、既存のトポロジーに新たな配送経路を設定します。

 以下は、先ほどと同様の Exchange と Queue が 2 つになった例を表します。

 先ほどの例との違いは、Pub2/Sub2 が追加されたことに加えて、Sub1 のキューの Routing Key が ‘r1’ から ‘*’ に変更されました。これは e1 に送られる全てのメッセージをキューイングすることを表します。
 新しいターミナルを起動させ、以下のコマンドを実行してください。

 さて、この例ではそれぞれの Exchange に送られたメッセージは、別々のキューに格納され、Pub1 が送ったメッセージが Sub2 から取得されること(またはその逆)はありません。
 ここで別のターミナルを立ち上げ、以下のように e2 から e1 に Routing Key = ‘r2’ の e2e binding を設定します。

 これによって、先ほどの図は以下のようになります。

 e1 と e2 間で Routing Kye = ‘r2’ の新たな転送経路が設定されました。これにより Sub1 では Pub1 に加えて、Pub2 のメッセージも取得するようになります。
 以下のように、最初に実行した Pub1 から e1 へのメッセージ送信に加え、以下 Pub2 から e2 へのメッセージ送信を実施してみてください。

 Sub1 において、それぞれの Exchange ‘e1′,’e2’ に送られたメッセージ ‘foo’,’bar’ が取得できることが確認できると思います。

終わりに

 今回は e2e binding によってアプリケーションの変更なしに、柔軟なメッセージのルーティングトポロジーを動的に設定する方法を紹介しました。
 冒頭で述べたように RabbitMQ の適用範囲は広く、様々な場面で利用されています。SDK も Ruby, C/C++, Java, Python から、OCaml, Haskell に至るまで、ほぼあらゆる言語が提供 されており、AMQP の実装としてはデファクトスタンダードと言っていいと思います。
 ちなみに RabbitMQ 自体の開発言語は Erlang です。

PAGE TOP