MQ, イベント


こちらのブログでもお知らせしましたが、オープンソースカンファレンス 2016 Tokyo/Fallで、弊社大山がMQについて、お話をしてきました。
img_0339

ツチノコブログでも話題に上がるMQの話を聞いて私なりにかみ砕いてレポートしようと思います!

そもそもMQとは?

MQはえむきゅーと読みます。not もきゅ(*´ω`*)
アプリケーション上でプロセス間のやりとりをサポートする処理方式のことです。

osc2016-mq-3-638

プロセスAとプロセスBがそれぞれ処理を走らせた際、お互いの処理が終わるのを待たずに処理を終えることができます。
プロセスを放り込めばMQにお任せできるので、システムの裏側でよく使われている手法です。

なぜMQなのか?

osc2016-mq-17-638

分散システムを単純にできる!
システムを作るときにシンプルな構成にできることもポイント。
-高可用性がある
-拡張性がある

 

MQにはいろいろある!

osc2016-mq-19-638

代表的なプロトコル/実装の特徴を紹介

AMQP(RabbitMQ)
MQTT(ActiveMQ)
STOMP(NewtMQ)
Kafka
ZeroMQ

AMQP(RabbitMQ)

Advanced Message Queuing Protocol
柔軟なメッセージルーティングができる。

MQTT(ActiveMQ)

軽量なプログラム。
ヘッダサイズが小さく、確実にメッセージを発行してくれる。

Kafka

順序付け負荷分散の両方を提供できる。
メッセージを格納し、キャッシュするためのファイルシステムに依存している。

まとめ

  • 万能なMQはない!
  • 用途にあった適切なMQを選択するのが大事。
  • Broker-MQ でパフォーマンス出したかったら NATS 使えばいいと思う!
    (但し、諸所の制約 (トランザクションなし、永続化しない、at-most-once な到達保証) を許容できるならば…)

発表資料

MQ, OpenStack, Python

 こんにちは、インフラ本部の大山裕泰です。
 今日は OpenStack の共通ライブラリ群 oslo に含まれる olso.messaging を使って RPC over MQ 処理を実装してみます。なぜ RPC を MQ を介して行うと良いかについては 過去のエントリ で書きましたので、興味があれば読んでみてください。

oslo って何?

 oslo は OpenStack の各コンポーネント (OpenStack の最も大きなソフトウェアモジュールの単位) で共通で利用するライブラリを集めたプロジェクトになります。
 その昔の OpenStack では設定ファイルやコマンドライン引数の解析、メッセージパッシング、ロギングなどといった処理が各コンポーネント毎に個別に存在しており、開発・メンテナンスコストが掛かる問題がありました。
 そして Grizzly サイクルで共通ライブラリ群を提供する olso プロジェクトが発足し、Havana で設定ファイルとコマンドライン引数の解析を行なう oslo.config と、通知や RPC の仕組みを提供する oslo.messaging がリリースされました。現在では、国際化や並行処理など様々な機能が加わり、本稿執筆時点では 34 のライブラリ が存在します。

 oslo 自体は OpenStack のプロジェクトですが、用途は OpenStack に限定されません。例えば oslo.config は、設定ファイルとコマンドライン引数の解析を行なうライブラリとして広く知られており、NTT Communication などが中心となって開発している SDN Framework Ryu などで採用されています。
 今回紹介する oslo.messaging では、RabbitMQZeroMQ などの MQ システムを介した通知や RPC の仕組みを提供します。

なぜ oslo.messaging なのか?

 oslo.messaging の使い方について見て行く前に、oslo.messaging についての理解を深めるために、なぜこの仕組みを使うのかについて考えてみたいと思います。
 MQ を利用した通知や RPC の仕組みは、もちろん oslo.messaging が登場する以前から存在していました。RabbitMQ を使う場合には Pika という AMQP ライブラリを用いてそれらの処理を行なうことができます。また ZeroMQ を使う場合には、PyZMQ ライブラリを用い、Kafka にも 同様の Python ライブラリ が存在します。

 しかしこれらを用いることによって、ミドルウェアないはプロトコル依存の実装になってしまいます。
 oslo.messaging では、ミドルウェア・プロトコル非依存の通知、RPC の仕組みを提供しており、これによってユーザは様々な MQ システムを選択することができるようになりました。

oslo.messaging の使い方

 以降では oslo.messaging を使って、汎用的な RPC over MQ 処理を行なう方法を紹介します。サンプルとして以下のコードを利用します。

https://github.com/userlocalhost2000/oslo-messaging-examples

 コードの中身を見る前に、システムのアウトラインについて簡単に把握したいと思います。以下は oslo.messaging と他のシステムとの関連、及び内部アーキテクチャについて表した図になります。

 処理のおおまかな流れとしては、左上の “User Application” が “RPCClient” で定義されたインターフェイスを叩き、”Transport” オブジェクトで抽象化された MQ システム (RabbitMQ など) を経由して “Server” に登録された “Endpoint(s)” の処理を実行し結果を取得します。ここでは、oslo.messaging レイヤにおけるクライアント、サーバ側の内部の仕組みについて簡単に解説します。

 クライアント側では RPCClient オブジェクトがユーザアプリケーションに対して、RPC 処理の仕組みを提供します。また Transport オブジェクトが、RabbitMQ や Kafka , ZeroMQ などの各種 MOM を抽象化し、ユーザは RPCClient が提供するインターフェイスを通して MOM 非依存な RPC リクエストをサーバに送ることができます。
 サーバ側では、ユーザからの RPC 要求を処理する Server オブジェクトを生成します。その際、ユーザ側で各 PRC 要求に対応する処理 (Endpoint) を一つ以上実装してやり、それらを Server オブジェクト生成時に指定します。
 また Server 及び Endpoint(s) に対して RPC API の名前空間と互換 version について設定することができます。これらの設定を保持したものが Target オブジェクトになり Server 及び Endpoint にひも付きます。
 Endpoint オブジェクトにおいて Target を省略した場合には、グローバルな名前空間と version1.0 が暗黙的に設定されます。ただし Server オブジェクトにおいては Target オブジェクトを topic 及び server パラメータ付きで指定しなければなりません。
 Target オブジェクトの topic パラメータは、サーバが提供する API を識別するための項目で amqp ドライバにおいては名前付きキューの名前に対応します。クライアントはこの値だけを知っていれば、サーバの場所を意識せずにサーバに対して RPC 要求を送ることができます。
 また Target オブジェクトの server パラメータは、文字通り API を提供するサーバを指定する項目ですが、実際に存在するサーバのホスト名ないしは IP アドレスを指定しなければならないわけではく、サーバを指定する任意の文字列になります。これはクライアントが topic パラメータで指定した API を持つサーバ群のうち、特定のサーバに対してメッセージを送りたい場合に利用されるパラメータになり amqp ドライバにおいては、topic 名で指定した名前付きキューに加えて、”${topic}.${server}” の名前付きキューを生成します (${topic} と ${server} にはそれぞれパラメータの設定値が入ります)。

動かしてみる

 各データ構造の役割と関連がわかったところで、サンプルコードを見ながら実際に動かしてみます。
 まずサンプルコードを動かすために oslo.messaging をインストールします。またここでは Python 2.7.6 を使用しています、尚本稿執筆時点で OpenStack がサポートしている Python のバージョンは 2.7.x と 3.4.x になります [*1](https://wiki.openstack.org/wiki/Python3)。

 続いて次のサンプルコードを GitHub から取得します。

 サーバ側のコード src/server.py について簡単に解説します。以下がその抜粋になります。

 22 行目の get_rpc_server メソッドの呼び出しで Server オブジェクトを生成します。RPCClient からの RPC 要求に対して endpoints パラメータで指定したオブジェクトにマッチするメソッドを呼び出します。その際、エンドポイントオブジェクトに target メンバが設定されている場合には、namespace と version ネゴシエーションの確認を行います。target メンバが指定されていない場合には、デフォルトの namespace (=None) と version (=1.0) が内部的に設定されます。
 コールバックメソッド hoge() のパラメータはそれぞれ、ctx がユーザ定義のオブジェクト (ディレクトリ型) を表し、arg がユーザ定義の引数を表します。

 次にクライアント側のコード src/client.py についても簡単に見て行きます。以下がその抜粋になります。

 20 行目でユーザ定義の RPCClient ラッパーの hoge を呼び出し、内部的に 15 行目でサーバに対して hoge メソッドの呼び出しを行っています。その際 14 行目の prepare() メソッドの呼び出しで RPCClient オブジェクトが内部で持つ Target オブジェクトの namespace と version パラメータを上書きします。
 version パラメータがサーバ側のエンドポイント hoge() で指定した値と一致していないことに気づいたかもしれません。Server 側のバージョンネゴシエーション処理では、クライアントからの要求 version のメジャーバージョン (上の位) が一致しており、かつマイナーバージョン (下の位) がサーバで設定した値以下であれば、互換性がある要求として当該メソッドを実行し結果を返します。

 それでは、実際にこれらを実行してみます。次のようにターミナルからサーバスクリプト src/server.py を実行し、次の別のターミナルを開いてクライアントスクリプト src/client.py を実行してみてください。


(左:サーバ、右:クライアント)

 クライアント側からの呼び出しでサーバ側で定義したエンドポイント hoge() が呼び出され、クライアント側で実行結果が受け取られていることが確認できました。

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 はまだまだ速くなると思っています。

Kafka, MQ, NewtMQ, RabbitMQ, STOMP

 インフラ統括本部の大山裕泰です。以前に STOMP と rabbitmq-stomp について紹介しました。
 STOMP は AMQP などに比べて非常にシンプルなプロトコルのため、高速なメッセージ転送が行えると期待していまいたが、とてもイマイチな結果でした。
 「これはきっと実装の問題に違いない!」と推測し、ちょうど STOMP 1.2 対応の C 実装サーバも知られていなかったので、STOMP プトロコルによる高速なメッセージ転送を行う MQ サーバ NewtMQ (にゅーとえむきゅー) を C で実装してみました。

 - https://github.com/newtmq/newtmq-server

 こちら の簡易ベンチマークツールを使って、大量のメッセージを送受信するのに要する時間を計測しました。
 

 
 こちらの図は、それぞれのサーバで 64 ~ 32K のサイズのメッセージを 10 万回送受信する処理が完了するまでにかかった時間を比較しています。
 また、全てのブロックサイズにおける転送時間の平均において、送信処理と受信処理の内訳を表した図が以下になります。青が送信処理時間、緑が受信処理時間を表しています。
 

 
 NewtMQ が 4 割弱ほど RabbitMQ (AMQP) より早い結果になりました。
 Kafka の結果が悪いのは、Publisher から送られたメッセージをストレージに格納するために他の結果と比べて処理に時間がかかっているものと考えられます。
 なお、Kafka のベンチマークでは Publisher の ‘acks’ パラメータ の値を 0 に設定しています。’acks’ パラメータによって Publisher が送信したメッセージに対する応答メッセージ (Ack) を待ち受けるかどうかを設定します。Kafka Protocol 0.8.x までは、デフォルトで Ack を無視していましたが、0.9.0 からはデフォルトで Ack を待ち受けるようになりました。STOMP では、同様の確認応答を無視しており公平を期するために、ここでは ‘acks’ パラメータを明示的に 0 に設定しています。

 ここでは STOMP も Kafka も送信処理は確認応答を受け取らない設定をしている(リクエスト投げっぱなし)なので、むしろ重要なのは受信処理時間です。以下に、各ブロックサイズでの受信処理時間の結果を示します。

 メッセージサイズが小さいケースにおいて、Kafka が圧倒的に早いです。しかしメッセージサイズに比例して処理時間が大きくなります。恐らく Kafka のログ (メッセージを保持する箱) がファイルシステムのページキャッシュに乗っているケースでは高速になり、メッセージサイズが大きくなるにつれてページキャッシュに乗り切らなくなり、処理時間が増えているものと思われます。

 尚、検証した環境は以下のとおりです。
 
サーバスペック

CPU : Intel Core i7-6700 CPU @ 3.40GHz
RAM : 32GB
HDD : Intel SSD (512GB)
OS : Ubuntu14.04
rabbitmq-server : 3.6.1-1
rabbitmq-stomp : 3.6.1
Kafka : 2.11-0.9.0.1

クライアントスペック

CPU : Intel Core i5 @ 2.7 GHz
RAM : 16GB
Ruby : 2.2.2p95
stomp (RubyGem) : 1.3.5
bunny (RubyGem) : 2.3.1
ruby-kafka (RubyGem) : 0.3.6

 ただここで得た結果は、それぞれのソフトウェアの特徴のごく一部を切り出して比較したにすぎません。これによって NewtMQ が RabbitMQ や Kafka より良いということは決して言えません。
 ここで示した一部の性能は NewtMQ よりも Kafka の方が上回っていますし、RabbitMQ や Kafka が積み上げてきた実績には NewtMQ は遠く及びません。また機能面でも大きな差があります。

 Kafka ではクラスタによるハイアベイラブルでスケーラブルな環境 を組むことができ、またメッセージをストレージに格納させることで大容量のデータをキューに滞在させ、それらを高速に処理させることができます。
 また RabbitMQ は柔軟なメッセージルーティングを実現する機能性に加え、AMQP の実装としての高い実績を有しており、また rabbitmq-stomp も topic 転送や AMQP キューとの連携などといった多数のオリジナル拡張機能を有した高い機能性を持っています。
 更に、分散システムの中核を成す MOM においては高い信頼性と拡張性が要求されます。RabbitMQ ではメッセージの永続化、及び Lazy Queueクラスタリング 機能によってこうした要求に応えています。
 他にも 遠隔ノードへのメッセージ転送動的なメッセージルーティング など、メッセージ転送に関する様々な機能を提供してくれています。
 
 しかし実際に利用しているユーザ側としては「あるサービスから別のサービスにメッセージが正しく、早く、安定して転送できればそれで良い(他には特にいらん)」と考えるユーザが大部分なんじゃないかということを個人的な実感として思うようになりました。
 こうしたサプライヤー側の過剰とも言えなくもない機能やサービスは MOM の分野に限ったことではなく SDN やクラウドなどの分野においても個人的な実感として感じます。
 もちろん、ユーザ側の要件が複雑化していった際にはこうした恩恵にあずかるわけですが MOM の分野においては、ネットワークやストレージ、データベースなどと比べれば、いざダメだとなった時の乗り替えは比較的容易かと思います。

STOMP は柔軟性や機能性に欠けるプロトコルですが、広帯域・高信頼なネットワークに閉じた環境において、高速なメッセージの受け渡しを実現することができると踏んでおり、これを実現するために NewtMQ を作りました。今回の結果にって、こうした可能性が見えてきたんじゃないかと思っています。
(とはいえ、まだ全然使えるシロモノには仕上がっていませんので、メジャーバージョンが出るくらいまでは、どうか暖い目で見ていてくださいmm)

PAGE TOP