題名はここオマージュです。
背景と目的
勤めている会社では、メッセージングのミドルウェアとしてAMQPというプロトコルを利用しているRabbitMQを使用しています。
RabbitMQについては、公式サイトの説明が充実しているので、業務で使用を考えている方は
基本的にこちらを読むことをお勧めいたします。
ただ、自分で調べていて
- 公式サイトの内容は英語
- 日本語のサイトを調べようとしても情報が少なかったり散らばっている
- そもそもメッセージングのミドルウェアがどういったもので、どういう際に必要になるかの説明が少ない
ことで結構段取り時間がかかってしまいました。
そこで、RabbitMQに関わることとなった時の自分を想定しながら、
対象: 急にRabbitMQに関わるようになった人
効果: 公式サイトや英語サイト見て実装する前段階の(本当に簡単な)見取り図
所要時間: 10分
となればと思い、まとめてみます。
どうしてメッセージングのミドルウェアが必要となるのか
一言で言うと、アプリケーションが交互に絡み合う複雑なシステムでも
- 非同期
- スケール
- 耐障害性
のあるサービスを構築できるからです。
まず前提として、
- 1つのプロジェクト(WARファイル)に、全てのサービスが詰め込まれている形
だと、次のような問題が発生します。
- あるサービスで不具合が起きるとプロジェクト全体、ひいてはサービス全体に影響を及ぼす
- サービスごとの開発(コーディング・ビルド・デプロイ・CIなど)が難しい
- サービスごとにチームを組んでアジャイルに開発ができない
そのため、
- サービス単位ごとにプロジェクトおよびチームを組んで、各サービスの連携やDBとの連絡はAPIを用いて行う
という方針が複雑かつ大きなWebサービスをスケールかつアジャイルに開発していく中で有益です。
一方、内部でAPIを呼ぶ回数が増える分だけ負荷はかかりますので、プロトコルはシンプルにHTTP、もしくは軽量なメッセージ送信(今回でいうとAMQP・RabbitMQ)を使うことが推奨されます。
加えて、基幹システムを提供するSaaS会社として
- お客様のネットワーク環境が遅い場合でも入力は正確に行われないといけない
- お客様の増加に従って負荷が重くなった場合は、随時スケールしてスムーズな入力ができるようにしないといけない
- サーバ障害を起こしてはならない
ことが求められます。そのため、
- クライアント側のネットワークが遅くても非同期なのでサーバサイドに影響を与えない
- frontendとbackend/DBが疎結合となりスケールしやすい
- HA化により、あるサーバで障害が発生してもサービスに影響を与えない
という特長を持つAMQP RabbitMQを内部のAPIのプロトコルとして利用しています。
ここら辺の話は詳しく書くと長くなりますので、気になる方はRESTful WEBサービス、マイクロサービスなどの単語で検索して調べてみてください。
AMQPとは
AMQPとはAdvanced Message Queuing Protocolの略であり、ビジネスメッセージをアプリケーションや組織間で伝達するための公開規格です。
公式サイトによると、元々は金融機関向けに開発されたようで、バンク・オブ・アメリカ、JPMorgan、ドイツ銀行など欧米有力銀行で利用されているようです。
主な機能としては、異なる組織・異なるプラットフォーム・異なる時間(非同期処理)・異なる場所(離れている場所やネットワーク環境)でも、繋げることができる点に強みがあります。
現在のAMQPは1.0が最新版ですが、RabbitMQのサイトによると"completely different protocol than AMQP 0-9-1"らしく、
RabbitMQはAMQP 0-9-1をベースにしているとのことです。
AMQP公式サイトの最新情報
RabbitMQ内のAMQP 0-9-1の仕様書
具体的な内容についてはRabbitMQでの実装を見ながら、理解していきましょう。
参考ブログとしてはGREE Engineer's blogがExchagneの話中心ですが、わかりやすかったです。
RabbitMQとは
AMQPはprotocolに過ぎないので、これを実装したミドルウェアです。
使い方は、RabbitMQのチュートリアル で実装を見ながら学ぶのがわかりやすいですが、
もちろん 英語 なので、一番簡単な例を、一番簡単に紹介してみます。
RabbitMQの概要の概要
RabbitMQの使用にあたり、理解すべき登場人物は次の表で説明できます。
- Producer:
RabbitMQにて、メッセージを送るプログラムのことをProducerと呼びます。Pさん。 - Consumer:
RabbitMQにて、メッセージの受け取りを待つプログラムのことをConsumerと呼びます。Cさん。 - queue:
RabbitMQでのメールボックスで、メッセージを好きなだけ投函することができます。何人ものPが1つのqueueに投函することも、何人ものCが1つのqueueからメッセージを取り出すことも可能です。図中の赤い図形のやつです。
ここでRabbitMQはMessageBroker(郵便配達人)として働きます。
郵便配達人の仕事は、次の2つです。
- producersからメッセージを受け取ってconsumersに届ける
- 届ける際に、route,buffer,persistといったルールを設けて届ける
今回の簡単な例では2については触れませんが、主な役割である1について簡単な例で説明します。
RabbitMQでHello World!を送る
Hello Worldを送るシンプルな例を説明します。
ProducerによるSend側の処理
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); //1.Connection作成
factory.setHost("localhost"); //1.Connection作成
Connection connection = factory.newConnection(); //1.Conenction作成
Channel channel = connection.createChannel(); //2.Channel作成
channel.queueDeclare(QUEUE_NAME, false, false, false, null); //3.queue宣言
String message = "Hello World!"; //4.messageのpublish
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));//4.messageのpublish
System.out.println(" [x] Sent '" + message + "'");
channel.close(); //5.Channel閉じる
connection.close(); //6.Connection閉じる
}
}
- Connection作成
Rabbit MQサーバをインストールしているサーバとのConnectionを設定します。この場合ではlocalhostにRabbitMQをインストールしている設定です。 - Channel作成
基本的に接続し続けることの多いConnectionとは別に実際にmessageを送るためのChannelを作成します - queue宣言
Channelからmessageを送る対象となるqueueを宣言します - messageのpublish
queueに対して送りたいmessageをpublishします - Channel閉じる
channelを閉じます - Connection閉じる
Connectionを閉じます
[x] Sent 'Hello World!'
送れたっぽいですね。ちゃんと送れているかConsumer側で確認しましょう。
ConsumerによるReceive側の処理
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); //1.Connection作成
factory.setHost("localhost"); //1.Connection作成
Connection connection = factory.newConnection(); //2.Connection作成
Channel channel = connection.createChannel(); //2.Channel作成
channel.queueDeclare(QUEUE_NAME, false, false, false, null); //3.queue宣言
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
}; //4. DefaultConsumerをoverride
channel.basicConsume(QUEUE_NAME, true, consumer); //5. messageのconsume
}
}
- Connection作成
Sendのときと同様です - Channel作成
Sendのときと同様です - queue宣言
Sendのときと同様です - DefaultConsumerをoverride
Consumer特有のコードです。handleDeliveryをOverrideしてmessageに対する処理を記載します。 - messageのconsume
作成したChannel、宣言したqueueからmessageを受信します
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'Hello World!'
ちゃんと受け取れましたね。
アーキテクチャ設計方針・実装方針の見取り図
実際のWebサービスに実装する際は
- frontendサーバが"P"としてRabbitMQサーバ(queue)にpublishする
→RabbitMQサーバがqueueとしてメッセージを溜める
→backendサーバで立てたリスナーが"C"として随時RabbitMQサーバからメッセージを取り出す
という設計が基本になるかと思います。
また、実装方針としては、
- RabbitMQサーバを積みたいサーバにRabbitMQ Serverをインストール・設定
- RabbitMQでメッセージを送受信したいfrontendサーバ、 backendサーバにRabbitMQのAPI設定(pom.xmlでの依存関係の設定やjarファイルの設定)
- frontendサーバ側でのメッセージを送るコードを書く
- backendサーバ側でメッセージを受信するコードを書く
- backendサーバ側でメッセージを取得してくるリスナーを立てる
という手順が基本になるかと思います。
まずはローカルにRabbitMQを落として、RabbitMQのjarファイル設定して簡単なコードを書いてみてください。
また、サイバーエージェントのエンジニアブログを読むと、実装レベルでの理解が深まります。
ただし、自作ライブラリを使っていて、そのライブラリを乗せたgithubがリンク切れであることにご注意。
まとめ・今後の話題
いかがでしたか。
全体感を意識して書いていたら、RabbitMQのインストールすら出てこないという記事になってしまいました。
ただ、その引き換えに当初掲げた目標が達成できていれば何よりです。
達成できていなくても何だか知ったつもりになっていただければ問題ありません。
今回ご紹介出来た部分以外にも、RabbitMQはまだまだ面白い話題があります。
そのため、例えば、
- もっと現場に活かしたい: RabbitMQの実装HowTo・SpringFrameworkでの実装・リスナーの立て方・高速化ノウハウ
- もっと耐障害性を強めたい: RabbitMQ HA(Highly Availability)
- アーキテクチャ設計に活かしたい: AWS等での運用、マイクロサービス化
について、書きたいと思っています。