rabbitmq學習

RabbitMQ筆記

本文參考資料:http://blog.csdn.net/chwshuang/article/details/50521708
學習并添加自己的理解記錄的筆記
官網demo很詳細,地址:http://www.rabbitmq.com/getstarted.html


1.RabbitMQ簡介

AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,是應用層協議的一個開放標準,為面向消息的中間件設計。消息中間件主要用于組件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、隊列、路由(包括點對點和發布/訂閱)、可靠性、安全。

ConnectionFactory、Connection、Channel

ConnectionFactory、Connection、Channel都是RabbitMQ對外提供的API中最基本的對象。

Connection是RabbitMQ的socket鏈接,它封裝了socket協議相關部分邏輯。

ConnectionFactory為Connection的制造工廠。

Channel是我們與RabbitMQ打交道的最重要的一個接口,我們大部分的業務操作是在Channel這個接口中完成的,包括定義Queue、定義Exchange、綁定Queue與Exchange、發布消息等。

2.Hello World!

mac安裝rabbitmq

brew update
brew install rabbitmq

耐心等待,安裝完成后需要將/usr/local/sbin添加到$PATH,可以將下面這兩行加到~/.bash_profile:

# RabbitMQ Config
export PATH=$PATH:/usr/local/sbin

編輯完后:wq保存退出,使環境變量立即生效。

source ~/.bash_profile

啟動rabbitmq

rabbitmq-server

登錄Web管理界面

瀏覽器輸入localhost:15672,賬號密碼全輸入guest即可登錄。

Paste_Image.png

登錄后管理頁面

Paste_Image.png

消息生產者

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.helloworld;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
 * 消息生產者
 *
 */
public class P {

  private final static String QUEUE_NAME = "hello";

  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    // queue 的定義具有冪等性(一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相等)
    // 因此定義的queue已經存在,不會重復定義,且不能修改。
    channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    String message = "Hello World!";
    //  第一個參數就是交換器的名稱。如果輸入“”空字符串,表示使用默認的匿名交換器。
    //  第二個參數是【routingKey】路由線索
    //  匿名交換器規則:
    //  發送到routingKey名稱對應的隊列。
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
    System.out.println("P [x] Sent '" + message + "'");

    channel.close();
    connection.close();
  }
}

消息消費者

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.helloworld;

import com.rabbitmq.client.*;
import java.io.IOException;

/**
 * 消息消費者
 * 
 *
 */
public class C {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        // 我們在接收端也定義了hello隊列。這是為了確保,如果接收端先啟動的時候,隊列已經存在。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println("C [*] 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("C [x] Received '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

3.工作隊列

將創建一個工作隊列,將信息發送到多個消費者。這中分配方式主要場景是消費者需要根據消息中的內容進行業務邏輯處理,這種消息可以看成是一個任務指令,處理起來比較耗時,通過多個消費者來處理這些消息,來提高數據的吞吐能力。
工作隊列(即任務隊列)的主要思想是不用一直等待資源密集型的任務處理完成,這就像一個生產線,將半成品放到生產線中,然后在生產線后面安排多個工人同時對半成品進行處理,這樣比一個生產線對應一個工人的吞吐量大幾個數量級。

工廠任務安排者(生產者P)NewTask.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.queues;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;

public class NewTask {

    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] argv) throws java.io.IOException, Exception {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
//      分發消息
        for(int i = 0 ; i < 5; i++){
            String message = "Hello World! " + i;
            channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }
        channel.close();
        connection.close();
    }
}

工人(消費者C1和C2)Worker1.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.queues;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Worker1 {
    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        final Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
        System.out.println("Worker1 [*] Waiting for messages. To exit press CTRL+C");
        // 每次從隊列中獲取數量
        channel.basicQos(1);

        final 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("Worker1 [x] Received '" + message + "'");
                try {
                    doWork(message);
                } finally {
                    System.out.println("Worker1 [x] Done");
                    // 消息處理完成確認
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };
        // 消息消費完成確認
        channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
    }

    private static void doWork(String task) {
        try {
            Thread.sleep(1000); // 暫停1秒鐘
        } catch (InterruptedException _ignored) {
            Thread.currentThread().interrupt();
        }
    }
}

工人(消費者C1和C2)Worker2.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.queues;

import com.rabbitmq.client.*;
import java.io.IOException;

public class Worker2 {
    private static final String TASK_QUEUE_NAME = "task_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        final Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
        System.out.println("Worker2 [*] Waiting for messages. To exit press CTRL+C");
        // 每次從隊列中獲取數量
        channel.basicQos(1);

        final 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("Worker2 [x] Received '" + message + "'");
                try {
                    doWork(message);
                } finally {
                    System.out.println("Worker2 [x] Done");
                    // 消息處理完成確認
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };
        // 消息消費完成確認
        channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
    }

    /**
     * 任務處理
     * 
     * @param task
     *            void
     */
    private static void doWork(String task) {
        try {
            Thread.sleep(1000); // 暫停1秒鐘
        } catch (InterruptedException _ignored) {
            Thread.currentThread().interrupt();
        }
    }
}

消息輪詢分發

啟動工人(消費者)

Paste_Image.png

啟動工廠任務安排者(生產者)

Paste_Image.png

消費者【1】完成0、3、4

Paste_Image.png

消費者【2】完成1、2

Paste_Image.png

消息確認(RabbitMQ支持消息確認–ACK)

如果處理一條消息需要幾秒鐘的時間,你可能會想,如果在處理消息的過程中,消費者服務器、網絡、網卡出現故障掛了,那可能這條正在處理的消息或者任務就沒有完成,就會失去這個消息和任務。
為了確保消息或者任務不會丟失,RabbitMQ支持消息確認–ACK。ACK機制是消費者端從RabbitMQ收到消息并處理完成后,反饋給RabbitMQ,RabbitMQ收到反饋后才將此消息從隊列中刪除。如果一個消費者在處理消息時掛掉(網絡不穩定、服務器異常、網站故障等原因導致頻道、連接關閉或者TCP連接丟失等),那么他就不會有ACK反饋,RabbitMQ會認為這個消息沒有正常消費,會將此消息重新放入隊列中。如果有其他消費者同時在線,RabbitMQ會立即將這個消息推送給這個在線的消費者。這種機制保證了在消費者服務器故障的時候,能不丟失任何消息和任務。

消息持久化

如何確保消費者掛掉的情況下,任務不會消失。但是如果RabbitMQ服務器掛了呢?
如果你不告訴RabbitMQ,當RabbitMQ服務器掛了,她可能就丟失所有隊列中的消息和任務。如果你想讓RabbitMQ記住她當前的狀態和內容,就需要通過2件事來確保消息和任務不會丟失。
第一件事,在隊列聲明時,告訴RabbitMQ,這個隊列需要持久化:

boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);

已經定義的隊列,再次定義是無效的,這就是冪次原理。RabbitMQ不允許重新定義一個已有的隊列信息,也就是說不允許修改已經存在的隊列的參數。如果你非要這樣做,只會返回異常。
一個快速有效的方法就是重新聲明另一個名稱的隊列,不過這需要修改生產者和消費者的代碼,所以,在開發時,最好是將隊列名稱放到配置文件中。
這時,即使RabbitMQ服務器重啟,新隊列中的消息也不會丟失。
下面我們來看看新消息發送的代碼:

import com.rabbitmq.client.MessageProperties;

channel.basicPublish("", "task_queue",
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            message.getBytes());

關于消息持久化的說明
標記為持久化后的消息也不能完全保證不會丟失。雖然已經告訴RabbitMQ消息要保存到磁盤上,但是理論上,RabbitMQ已經接收到生產者的消息,但是還沒有來得及保存到磁盤上,服務器就掛了(比如機房斷電),那么重啟后,RabbitMQ中的這條未及時保存的消息就會丟失。因為RabbitMQ不做實時立即的磁盤同步(fsync)。這種情況下,對于持久化要求不是特別高的簡單任務隊列來說,還是可以滿足的。如果需要更強大的保證,那么你可以考慮使用生產者確認反饋機制。

負載均衡

默認情況下,RabbitMQ將隊列消息隨機分配給每個消費者,這時可能出現消息調度不均衡的問題。例如有兩臺消費者服務器,一個服務器可能非常繁忙,消息不斷,另外一個卻很悠閑,沒有什么負載。RabbitMQ不會主動介入這些情況,還是會隨機調度消息到每臺服務器。
這是因為RabbitMQ此時只負責調度消息,不會根據ACK的反饋機制來分析那臺服務器返回反饋慢,是不是處理不過來啊。
為了解決這個問題,我們可以使用【prefetchcount = 1】這個設置。這個設置告訴RabbitMQ,不要一次將多個消息發送給一個消費者。這樣做的好處是只有當消費者處理完成當前消息并反饋后,才會收到另外一條消息或任務。這樣就避免了負載不均衡的事情了。

int prefetchCount = 1;
channel.basicQos(prefetchCount);

RabbitMQ:

  • 生產者是發送消息的應用程序
  • 隊列是存儲消息的緩沖區
  • 消費者是接收消息的應用程序

4.發布訂閱

“發布/訂閱”模式的基礎是將消息廣播到所有的接收器上。

交換器

實際上,RabbitMQ中消息傳遞模型的核心思想是:生產者不直接發送消息到隊列。實際的運行環境中,生產者是不知道消息會發送到那個隊列上,她只會將消息發送到一個交換器,交換器也像一個生產線,她一邊接收生產者發來的消息,另外一邊則根據交換規則,將消息放到隊列中。交換器必須知道她所接收的消息是什么?它應該被放到那個隊列中?它應該被添加到多個隊列嗎?還是應該丟棄?這些規則都是按照交換器的規則來確定的。

交換器的規則有:

  • direct (直連)
  • topic (主題)
  • headers (標題)
  • fanout (分發)也有翻譯為扇出的。

如使用【fanout】類型創建一個名稱為 logs的交換器:

channel.exchangeDeclare("logs", "fanout");

列出服務器上所有可用的交換器:

rabbitmqctl list_exchanges
Paste_Image.png

以【amq.*】開頭的交換器都是RabbitMQ默認創建的。在生產環境中,可以自己定義。

匿名交換器

我們知道,發送消息到隊列時根本沒有使用交換器,但是消息也能發送到隊列。這是因為RabbitMQ選擇了一個空“”字符串的默認交換器。
來看看我們之前的代碼:

channel.basicPublish("", "hello", null, message.getBytes());

第一個參數就是交換器的名稱。如果輸入“”空字符串,表示使用默認的匿名交換器。
第二個參數是【routingKey】路由線索
匿名交換器規則:
發送到routingKey名稱對應的隊列。

臨時隊列

如果要在生產者和消費者之間創建一個新的隊列,又不想使用原來的隊列,臨時隊列就是為這個場景而生的:

  • 首先,每當我們連接到RabbitMQ,我們需要一個新的空隊列,我們可以用一個隨機名稱來創建,或者說讓服務器選擇一個隨機隊列名稱給我們。
  • 一旦我們斷開消費者,隊列應該立即被刪除。

Java客戶端,提供queuedeclare()為我們創建一個非持久化、獨立、自動刪除的隊列名稱。

String queueName = channel.queueDeclare().getQueue();

通過上面的代碼就能獲取到一個隨機隊列名稱。 例如:它可能是:amq.gen-jzty20brgko-hjmujj0wlg。

綁定

將我們的隊列跟交換器進行綁定:

channel.queueBind(queueName, "logs", "");

執行完這段代碼后,日志交換器會將消息添加到我們的隊列中。

**獲取綁定列表 **

rabbitmqctl list_bindings
Paste_Image.png

發布者EmitLog.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.publish;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * 在建立連接后,我們聲明了一個交互。
 * 如果當前沒有隊列被綁定到交換器,消息將被丟棄,因為沒有消費者監聽,這條消息將被丟棄。
 */
public class EmitLog {

    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        // direct (直連)、topic (主題)、headers (標題)、fanout (分發)也有翻譯為扇出的。
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        // 分發消息
        for(int i = 0 ; i < 5; i++){
            String message = "Hello World! " + i;
             // 與之前不同的是它不是將消息發送到匿名交換器中,而是發送到一個名為【logs】的交換器中
             // 我們提供一個空字符串的routingkey,它的功能被交換器的分發類型【fanout】代替了
             channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
             System.out.println(" [x] Sent '" + message + "'");
        }
        channel.close();
        connection.close();
    }
}

訂閱者ReceiveLogs1.java同ReceiveLogs2.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.publish;

import com.rabbitmq.client.*;

import java.io.IOException;

public class ReceiveLogs1 {
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 在Java客戶端,提供queueDeclare()為我們創建一個非持久化、獨立、自動刪除的隊列名稱。
        String queueName = channel.queueDeclare().getQueue();
        // 將我們的隊列跟交換器進行綁定
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        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 + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

結果:ReceiveLogs1和ReceiveLogs2都收到了EmitLog發送的消息。
使用【rabbitmqctl list_bindings】命令可以看到兩個臨時隊列的名稱:

Paste_Image.png

4.消息路由

綁定關系

綁定是交換器和隊列之間的一種關系,用戶微博,微信的例子可以簡單的理解為關注

綁定關系中使用的路由關鍵字【routingkey】是否有效取決于交換器的類型。如果交換器是分發【fanout】類型,就會忽略路由關鍵字【routingkey】的作用。

直連類型交換器

通過分發【fanout】類型的交換器【logs】廣播日志信息,現在我們將日志分debug、info、warn、error這幾種基本的級別,實際在生產環境中,避免磁盤空間浪費,應用只會將error級別的日志打印出來。而分發【fanout】類型的交換器會將所有基本的日志都發送出來,如果我們想只接收某一級別的日志信息,就需要使用直連【direct】類型的交換器了

多重綁定

我們允許多個隊列以相同的路由關鍵字綁定到同一個交換器中,可以看到,交換器雖然是直連類型,但是綁定后的效果卻跟分發類型的交換器類似,相同的是隊列1和隊列2都會收到同一條來自交換器的消息。
他們的區別:分發模式下,隊列1、隊列2會收到所有級別(除ERROR級別以外)的消息,而直連模式下,他們僅僅只會收到ERROR關鍵字類型的消息。


RoutingSendDirect.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.routing;

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;

public class RoutingSendDirect {

    private static final String EXCHANGE_NAME = "direct_logs";
 // 路由關鍵字
    private static final String[] routingKeys = new String[]{"info" ,"warning", "error"};
    
    public static void main(String[] argv) throws Exception {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明交換器 direct表示直連
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//      發送消息
        for(String severity :routingKeys){
            String message = "Send the message level:" + severity;
            channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
            System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
        }
        channel.close();
        connection.close();
    }
}

ReceiveLogsDirect1.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.routing;

import com.rabbitmq.client.*;

import java.io.IOException;

public class ReceiveLogsDirect1 {
    // 交換器名稱
    private static final String EXCHANGE_NAME = "direct_logs";
    // 路由關鍵字
    private static final String[] routingKeys = new String[]{"info" ,"warning", "error"};
    
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//      獲取匿名隊列名稱
        String queueName = channel.queueDeclare().getQueue();
//      根據路由關鍵字進行多重綁定
        for (String severity : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, severity);
            System.out.println("ReceiveLogsDirect1 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + severity);
        }
        System.out.println("ReceiveLogsDirect1 [*] 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 '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

ReceiveLogsDirect2.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.routing;

import com.rabbitmq.client.*;

import java.io.IOException;

public class ReceiveLogsDirect2 {
    // 交換器名稱
    private static final String EXCHANGE_NAME = "direct_logs";
    // 路由關鍵字
    private static final String[] routingKeys = new String[]{"error"};
    
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//      獲取匿名隊列名稱
        String queueName = channel.queueDeclare().getQueue();
//      根據路由關鍵字進行多重綁定
        for (String severity : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, severity);
            System.out.println("ReceiveLogsDirect2 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + severity);
        }
        System.out.println("ReceiveLogsDirect2 [*] 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 '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

運行ReceiveLogsDirect1和ReceiveLogsDirect2

Paste_Image.png
Paste_Image.png

運行RoutingSendDirect發送消息

Paste_Image.png

查看ReceiveLogsDirect1結果

Paste_Image.png

查看ReceiveLogsDirect2結果


Paste_Image.png

隊列1收到了所有的消息,隊列2只收到了error級別的消息。這與我們的預期一樣。

5.Topic模式

topic類型的交換器允許在RabbitMQ中使用模糊匹配來綁定自己感興趣的信息。
如果我想只接收生產者com.test.rabbitmq.topic包下的日志,其他包的忽略掉,之前的日志系統處理起來可能就非常麻煩,還好,我們有匹配模式,現在我們將生產者發送過來的消息按照包名來命名,那么消費者端就可以在匹配模式下使用【#.topic.*】這個路由關鍵字來獲得感興趣的消息。

匹配交換器

通過匹配交換器,我們可以配置更靈活的消息系統,你可以在匹配交換器模式下發送這樣的路由關鍵字:
“a.b.c”、“c.d”、“quick.orange.rabbit”
不過一定要記住,路由關鍵字【routingKey】不能超過255個字節(bytes)
匹配交換器的匹配符
*(星號)表示一個單詞
#(井號)表示零個或者多個單詞

交換器在匹配模式下:

如果消費者端的路由關鍵字只使用【#】來匹配消息,在匹配【topic】模式下,它會變成一個分發【fanout】模式,接收所有消息。
如果消費者端的路由關鍵字中沒有【#】或者【*】,它就變成直連【direct】模式來工作。

TopicSend.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.topic;

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;

public class TopicSend {

    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] argv) {
        Connection connection = null;
        Channel channel = null;
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");

            connection = factory.newConnection();
            channel = connection.createChannel();
//          聲明一個匹配模式的交換器
            channel.exchangeDeclare(EXCHANGE_NAME, "topic");

            // 待發送的消息
            String[] routingKeys = new String[]{"quick.orange.rabbit", 
                                                "lazy.orange.elephant", 
                                                "quick.orange.fox", 
                                                "lazy.brown.fox", 
                                                "quick.brown.fox", 
                                                "quick.orange.male.rabbit", 
                                                "lazy.orange.male.rabbit"};
//          發送消息
            for(String severity :routingKeys){
                String message = "From "+severity+" routingKey' s message!";
                channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
                System.out.println("TopicSend [x] Sent '" + severity + "':'" + message + "'");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ignore) {
                }
            }
        }
    }
}

ReceiveLogsTopic1.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.topic;

import com.rabbitmq.client.*;
import java.io.IOException;

public class ReceiveLogsTopic1 {

    private static final String EXCHANGE_NAME = "topic_logs";
     
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明一個匹配模式的交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = channel.queueDeclare().getQueue();
        // 路由關鍵字
        String[] routingKeys = new String[]{"*.orange.*"};
//      綁定路由關鍵字
        for (String bindingKey : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
            System.out.println("ReceiveLogsTopic1 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + bindingKey);
        }

        System.out.println("ReceiveLogsTopic1 [*] 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("ReceiveLogsTopic1 [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

ReceiveLogsTopic2.java

/**
 * TODO
 * 
 */
package com.aitongyi.rabbitmq.topic;

import com.rabbitmq.client.*;
import java.io.IOException;

public class ReceiveLogsTopic2 {

    private static final String EXCHANGE_NAME = "topic_logs";
     
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明一個匹配模式的交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = channel.queueDeclare().getQueue();
        // 路由關鍵字
        String[] routingKeys = new String[]{"*.*.rabbit", "lazy.#"};
//      綁定路由關鍵字
        for (String bindingKey : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
            System.out.println("ReceiveLogsTopic2 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + bindingKey);
        }

        System.out.println("ReceiveLogsTopic2 [*] 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("ReceiveLogsTopic2 [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

運行結果

TopicSend發送7條數據


Paste_Image.png

ReceiveLogsTopic1接收3條


Paste_Image.png

ReceiveLogsTopic1接收4條


Paste_Image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 什么叫消息隊列 消息(Message)是指在應用間傳送的數據。消息可以非常簡單,比如只包含文本字符串,也可以更復雜...
    lijun_m閱讀 1,366評論 0 1
  • 來源 RabbitMQ是用Erlang實現的一個高并發高可靠AMQP消息隊列服務器。支持消息的持久化、事務、擁塞控...
    jiangmo閱讀 10,408評論 2 34
  • 關于消息隊列,從前年開始斷斷續續看了些資料,想寫很久了,但一直沒騰出空,近來分別碰到幾個朋友聊這塊的技術選型,是時...
    預流閱讀 585,481評論 51 786
  • 來溫哥華參觀的朋友讓我談談溫哥華的規劃經驗。 我的第一反應是溫哥華并不是被各種法令“規劃”出來的,反而像是由不同社...
    布拉格向北閱讀 1,723評論 2 3