RabbitMQ入門

一:入門

1.安裝Erlang

2.安裝RabbitMQ

3.配置

激活 RabbitMQ’s Management Plugin


C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmq-plugins.bat enable rabbitmq
Error: The following plugins could not be found:
  rabbitmq


C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmq-plugins.bat enable rabbitmq_management
The following plugins have been enabled:
  amqp_client
  cowlib
  cowboy
  rabbitmq_web_dispatch
  rabbitmq_management_agent
  rabbitmq_management

Applying plugin configuration to rabbit@DESKTOP-BugYang... started 6 plugins.

C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>net stop RabbitMQ && net start RabbitMQ
RabbitMQ 服務正在停止......
RabbitMQ 服務已成功停止。

RabbitMQ 服務正在啓動 .
RabbitMQ 服務已經啓動成功。


C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>rabbitmqctl.bat list_users
Listing users
guest   [administrator]

C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.12\sbin>
http://localhost:15672/

賬號密碼:guest

4.下載maven

<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>4.1.0</version>
</dependency>

5.創建發送者

public class Send {

    //隊列名稱
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws java.io.IOException, TimeoutException {
        /**
         * 創建連接連接到MabbitMQ
         */
        ConnectionFactory factory = new ConnectionFactory();
        //設置MabbitMQ所在主機ip或者主機名
        factory.setHost("localhost");
        //創建一個連接
        Connection connection = factory.newConnection();
        //創建一個頻道
        Channel channel = connection.createChannel();
        //指定一個隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //發送的消息
        String message = "hello world!";
        //往隊列中發出一條消息
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        //關閉頻道和連接
        channel.close();
        connection.close();
    }
}

打印

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
 [x] Sent 'hello world!'

Process finished with exit code 0

6.創建接受者

public class Rec {
    //隊列名稱
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws java.io.IOException,
            java.lang.InterruptedException, TimeoutException {
        //打開連接和創建頻道,與發送端一樣
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //聲明隊列,主要爲了防止消息接收者先運行此程序,隊列還不存在時創建隊列。
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //創建隊列消費者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //指定消費隊列
        channel.basicConsume(QUEUE_NAME, true, consumer);
        while (true)
        {
            //nextDelivery是一個阻塞方法(內部實現其實是阻塞隊列的take方法)
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");
        }

    }
}

打印

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
 [*] Waiting for messages. To exit press CTRL+C
 [x] Received 'hello world!'

二:工作隊列

1.發送消息

public class NewTask
{
    //隊列名稱
    private final static String QUEUE_NAME = "workqueue";

    public static void main(String[] args) throws IOException, TimeoutException {

        //創建連工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建隊列
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //發送10條消息,依次在消息後面附加1-10個點
        for (int i = 0; i < 10; i++)
        {
            String dots = "";
            for (int j = 0; j <= i; j++)
            {
                dots += ".";
            }
            //拼數據
            String message = "helloworld" + dots+dots.length();
            //推送到rabbitmq中
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            //推送完成,打印結束語句
            System.out.println(" [x] Sent '" + message + "'");
        }
        //關閉隊列
        channel.close();
        //關閉消息
        connection.close();

    }


}
 [x] Sent 'helloworld.1'
 [x] Sent 'helloworld..2'
 [x] Sent 'helloworld...3'
 [x] Sent 'helloworld....4'
 [x] Sent 'helloworld.....5'
 [x] Sent 'helloworld......6'
 [x] Sent 'helloworld.......7'
 [x] Sent 'helloworld........8'
 [x] Sent 'helloworld.........9'
 [x] Sent 'helloworld..........10'

2.接收消息

運行兩個Work類

public class Work
{
    //隊列名稱
    private final static String QUEUE_NAME = "workqueue";

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

        //區分不同工作進程的輸出
        int hashCode = Work.class.hashCode();

        //創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建隊列
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(hashCode
                + " [*] Waiting for messages. To exit press CTRL+C");

        QueueingConsumer consumer = new QueueingConsumer(channel);
       // 指定消費隊列
        //關閉應答機制,會丟失消息
        channel.basicConsume(QUEUE_NAME, true, consumer);
        //打開應答機制,不會丟失消息
        channel.basicConsume(QUEUE_NAME, false, consumer);
        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());

            System.out.println(hashCode + " [x] Received '" + message + "'");
//            doWork(message);
            System.out.println(hashCode + " [x] Done");

        }

    }

    /**
     * 每個點耗時1s
     * @param task
     * @throws InterruptedException
     */
    private static void doWork(String task) throws InterruptedException
    {
        for (char ch : task.toCharArray())
        {
            if (ch == '.')
                Thread.sleep(1000);
        }
    }
}
746292446 [x] Received 'helloworld.1'
746292446 [x] Done
746292446 [x] Received 'helloworld...3'
746292446 [x] Done
746292446 [x] Received 'helloworld.....5'
746292446 [x] Done
746292446 [x] Received 'helloworld.......7'
746292446 [x] Done
746292446 [x] Received 'helloworld.........9'
746292446 [x] Done
242131142 [x] Received 'helloworld..2'
242131142 [x] Done
242131142 [x] Received 'helloworld....4'
242131142 [x] Done
242131142 [x] Received 'helloworld......6'
242131142 [x] Done
242131142 [x] Received 'helloworld........8'
242131142 [x] Done
242131142 [x] Received 'helloworld..........10'
242131142 [x] Done

可以看到,默認的,RabbitMQ會一個一個的發送信息給下一個消費者(consumer),而不考慮每個任務的時長等等,且是一次性分配,並非一個一個分配。平均的每個消費者將會獲得相等數量的消息。這樣分發消息的方式叫做round-robin。

3.消息應答(message acknowledgments)

我們首先開啓兩個任務,然後執行發送任務的代碼(NewTask.java),然後立即關閉第二個任務,兩個加起來打印出來的數據會有缺失

一旦RabbItMQ交付了一個信息給消費者,會馬上從內存中移除這個信息。在這種情況下,如果殺死正在執行任務的某個工作者,我們會丟失它正在處理的信息。我們也會丟失已經轉發給這個工作者且它還未執行的消息。

爲了保證消息永遠不會丟失,RabbitMQ支持消息應答(message acknowledgments)。

  • 消費者發送應答給RabbitMQ,告訴它信息已經被接收和處理,然後RabbitMQ可以自由的進行信息刪除。
  • 如果消費者被殺死而沒有發送應答,RabbitMQ會認爲該信息沒有被完全的處理,然後將會重新轉發給別的消費者。通過這種方式,你可以確認信息不會被丟失,即使消者偶爾被殺死。
  • 這種機制並沒有超時時間這麼一說,RabbitMQ只有在消費者連接斷開是重新轉發此信息。如果消費者處理一個信息需要耗費特別特別長的時間是允許的。

消息應答默認是打開的。上面的代碼中我們通過顯示的設置autoAsk=true關閉了這種機制。

boolean ack = false ; //打開應答機制  
channel.basicConsume(QUEUE_NAME, ack, consumer);  
//另外需要在每次處理完成一個消息後,手動發送一次應答。  
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);  
public class Work  
{  
    //隊列名稱  
    private final static String QUEUE_NAME = "workqueue";  

    public static void main(String[] argv) throws java.io.IOException,  
            java.lang.InterruptedException  
    {  
        //區分不同工作進程的輸出  
        int hashCode = Work.class.hashCode();  
        //創建連接和頻道  
        ConnectionFactory factory = new ConnectionFactory();  
        factory.setHost("localhost");  
        Connection connection = factory.newConnection();  
        Channel channel = connection.createChannel();  
        //聲明隊列  
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);  
        System.out.println(hashCode  
                + " [*] Waiting for messages. To exit press CTRL+C");  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定消費隊列  
        boolean ack = false ; //打開應答機制  
        channel.basicConsume(QUEUE_NAME, ack, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  

            System.out.println(hashCode + " [x] Received '" + message + "'");  
            doWork(message);  
            System.out.println(hashCode + " [x] Done");  
            //發送應答  
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);  

        }  

    }  
} 

4.消息持久化(Message durability)

我們已經學習了即使消費者被殺死,消息也不會被丟失。但是如果此時RabbitMQ服務被停止,我們的消息仍然會丟失

當RabbitMQ退出或者異常退出,將會丟失所有的隊列和信息,除非你告訴它不要丟失。

我們需要做兩件事來確保信息不會被丟失:我們需要給所有的隊列消息設置持久化的標誌。

  • 第一, 我們需要確認RabbitMQ永遠不會丟失我們的隊列。爲了這樣,我們需要聲明它爲持久化的。
boolean durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);

注:RabbitMQ不允許使用不同的參數重新定義一個隊列,所以已經存在的隊列,我們無法修改其屬性。

  • 第二, 我們需要標識我們的信息爲持久化的。通過設置MessageProperties(implements BasicProperties)值爲PERSISTENT_TEXT_PLAIN。
channel.basicPublish("", "task_queue",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

現在你可以執行一個發送消息的程序,然後關閉服務,再重新啓動服務,運行消費者程序做下實驗。

5.公平轉發(Fair dispatch)

對於兩個消費者,有一系列的任務,奇數任務特別耗時,而偶數任務卻很輕鬆,這樣造成一個消費者一直繁忙,另一個消費者卻很快執行完任務後等待。
造成這樣的原因是因爲RabbitMQ僅僅是當消息到達隊列進行轉發消息。並不在乎有多少任務消費者並未傳遞一個應答給RabbitMQ。僅僅盲目轉發所有的奇數給一個消費者,偶數給另一個消費者。

int prefetchCount = 1;  
channel.basicQos(prefetchCount);  
public class NewTask  
{  
    // 隊列名稱  
    private final static String QUEUE_NAME = "workqueue_persistence";  

    public static void main(String[] args) throws IOException  
    {  
        // 創建連接和頻道  
        ConnectionFactory factory = new ConnectionFactory();  
        factory.setHost("localhost");  
        Connection connection = factory.newConnection();  
        Channel channel = connection.createChannel();  
        // 聲明隊列  
        boolean durable = true;// 1、設置隊列持久化  
        channel.queueDeclare(QUEUE_NAME, durable, false, false, null);  
        // 發送10條消息,依次在消息後面附加1-10個點  
        for (int i = 5; i > 0; i--)  
        {  
            String dots = "";  
            for (int j = 0; j <= i; j++)  
            {  
                dots += ".";  
            }  
            String message = "helloworld" + dots + dots.length();  
            // MessageProperties 2、設置消息持久化  
            channel.basicPublish("", QUEUE_NAME,  
                    MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());  
            System.out.println(" [x] Sent '" + message + "'");  
        }  
        // 關閉頻道和資源  
        channel.close();  
        connection.close();  

    }  

} 
public class Work  
{  
    // 隊列名稱  
    private final static String QUEUE_NAME = "workqueue_persistence";  

    public static void main(String[] argv) throws java.io.IOException,  
            java.lang.InterruptedException  
    {  
        // 區分不同工作進程的輸出  
        int hashCode = Work.class.hashCode();  
        // 創建連接和頻道  
        ConnectionFactory factory = new ConnectionFactory();  
        factory.setHost("localhost");  
        Connection connection = factory.newConnection();  
        Channel channel = connection.createChannel();  
        // 聲明隊列  
        boolean durable = true;  
        channel.queueDeclare(QUEUE_NAME, durable, false, false, null);  
        System.out.println(hashCode  
                + " [*] Waiting for messages. To exit press CTRL+C");  
        //設置最大服務轉發消息數量  
        int prefetchCount = 1;  
        channel.basicQos(prefetchCount);  
        QueueingConsumer consumer = new QueueingConsumer(channel);  
        // 指定消費隊列  
        boolean ack = false; // 打開應答機制  
        channel.basicConsume(QUEUE_NAME, ack, consumer);  
        while (true)  
        {  
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();  
            String message = new String(delivery.getBody());  

            System.out.println(hashCode + " [x] Received '" + message + "'");  
            doWork(message);  
            System.out.println(hashCode + " [x] Done");  
            //channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);  
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);  

        }  

    }  

    /** 
     * 每個點耗時1s 
     *  
     * @param task 
     * @throws InterruptedException 
     */  
    private static void doWork(String task) throws InterruptedException  
    {  
        for (char ch : task.toCharArray())  
        {  
            if (ch == '.')  
                Thread.sleep(1000);  
        }  
    }  
}  

三:發佈/訂閱

工作隊列中的一個任務只會發給一個工作者

就是把一個消息發給多個消費者,這種模式稱之爲發佈/訂閱(類似觀察者模式)。

爲了驗證這種模式,我們準備構建一個簡單的日誌系統。這個系統包含兩類程序,

一類程序發動日誌,另一類程序接收和處理日誌。

我們實現,一個接收者將接收到的數據寫到硬盤上,與此同時,另一個接收者把接收到的消息展現在屏幕上。

1:轉發器(Exchanges)

RabbitMQ消息模型的核心理念是生產者永遠不會直接發送任何消息給隊列,一般的情況生產者甚至不知道消息應該發送到哪些隊列。

相反的,生產者只能發送消息給轉發器(Exchange)。轉發器是非常簡單的,一邊接收從生產者發來的消息,另一邊把消息推送到隊列中。轉發器必須清楚的知道消息如何處理它收到的每一條消息。是否應該追加到一個指定的隊列?是否應該追加到多個隊列?或者是否應該丟棄?這些規則通過轉發器的類型進行定義。

可用的轉發器類型:

  • Direct
  • Topic
  • Headers
  • Fanout

聲明轉發器類型的代碼:

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

fanout類型轉發器特別簡單,把所有它介紹到的消息,廣播到所有它所知道的隊列。不過這正是我們前述的日誌系統所需要的

2、匿名轉發器(nameless exchange)

前面說到生產者只能發送消息給轉發器(Exchange),但是我們前兩篇博客中的例子並沒有使用到轉發器,我們仍然可以發送和接收消息。

這是因爲我們使用了一個默認的轉發器,它的標識符爲””。之前發送消息的代碼:

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

第一個參數爲轉發器的名稱,我們設置爲”” : 如果存在routingKey(第二個參數),消息由routingKey決定發送到哪個隊列。

現在我們可以指定消息發送到的轉發器:

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

3、臨時隊列(Temporary queues)

前面的博客中我們都爲隊列指定了一個特定的名稱。能夠爲隊列命名對我們來說是很關鍵的,我們需要指定消費者爲某個隊列。當我們希望在生產者和消費者間共享隊列時,爲隊列命名是很重要的。
不過,對於我們的日誌系統我們並不關心隊列的名稱。我們想要接收到所有的消息,而且我們也只對當前正在傳遞的數據的感興趣。爲了滿足我們的需求,需要做兩件事:
第一, 無論什麼時間連接到Rabbit我們都需要一個新的空的隊列。爲了實現,我們可以使用隨機數創建隊列,或者更好的,讓服務器給我們提供一個隨機的名稱。
第二, 一旦消費者與Rabbit斷開,消費者所接收的那個隊列應該被自動刪除。
Java中我們可以使用queueDeclare()方法,不傳遞任何參數,來創建一個非持久的、唯一的、自動刪除的隊列且隊列名稱由服務器隨機產生。

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

一般情況這個名稱與amq.gen-JzTY20BRgKO-HjmUJj0wLg 類似

4、綁定(Bindings)

我們已經創建了一個fanout轉發器和隊列,我們現在需要通過binding告訴轉發器把消息發送給我們的隊列。

channel.queueBind(queueName, “logs”, ””)

參數1:隊列名稱 ;參數2:轉發器名稱

5、完整的例子

1.創建發送器
public class EmitLog
{
    private final static String EXCHANGE_NAME = "ex_log";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建頻道
        Channel channel = connection.createChannel();
        // 聲明轉發器和類型
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout" );
        //創建發送的數據
        String message = new Date().toLocaleString()+" : log something";
        // 往轉發器上發送消息
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());

        System.out.println(" [x] Sent '" + message + "'");

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

    }

}
2.創建接收器,數據寫進文件裏
public class ReceiveLogsToSave
{
    private final static String EXCHANGE_NAME = "ex_log";

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

        // 創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建頻道
        Channel channel = connection.createChannel();
        // 聲明轉發器和類型
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 創建一個非持久的、唯一的且自動刪除的隊列,臨時隊列
        String queueName = channel.queueDeclare().getQueue();
        // 爲轉發器指定隊列,設置binding,綁定
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 指定接收者,第二個參數爲自動應答,無需手動應答
        channel.basicConsume(queueName, true, consumer);

        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());

            print2File(message);
        }

    }

    private static void print2File(String msg)
    {
        try
        {
            String dir = ReceiveLogsToSave.class.getClassLoader().getResource("").getPath();
            String logFileName = new SimpleDateFormat("yyyy-MM-dd")
                    .format(new Date());
            File file = new File(dir, logFileName+".txt");
            FileOutputStream fos = new FileOutputStream(file, true);
            fos.write((msg + "\r\n").getBytes());
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e)
        {
            e.printStackTrace();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}
3.創建接收器,打印出信息
public class ReceiveLogsToConsole
{
    private final static String EXCHANGE_NAME = "ex_log";

    public static void main(String[] argv) throws java.io.IOException,
            java.lang.InterruptedException, TimeoutException {
        // 創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建頻道
        Channel channel = connection.createChannel();
        // 聲明轉發器和類型
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 創建一個非持久的、唯一的且自動刪除的隊列
        String queueName = channel.queueDeclare().getQueue();
        // 爲轉發器指定隊列,設置binding
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 指定接收者,第二個參數爲自動應答,無需手動應答
        channel.basicConsume(queueName, true, consumer);

        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [x] Received '" + message + "'");

        }

    }

}

四:路由(Routing)

需求:本篇博客我們準備給日誌系統添加新的特性,讓日誌接收者能夠訂閱部分消息。例如,我們可以僅僅將致命的錯誤寫入日誌文件,然而仍然在控制面板上打印出所有的其他類型的日誌消息。

1、綁定(Bindings)

在上一篇博客中我們已經使用過綁定

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

綁定表示轉發器與隊列之間的關係。

我們也可以簡單的認爲:隊列對該轉發器上的消息感興趣。

綁定可以附帶一個額外的參數routingKey。爲了與避免basicPublish方法(發佈消息的方法)的參數混淆,我們準備把它稱作綁定鍵(binding key)。下面展示如何使用綁定鍵(binding key)來創建一個綁定:

channel.queueBind(queueName, EXCHANGE_NAME, "black");

綁定鍵的意義依賴於轉發器的類型。對於fanout類型,忽略此參數。

2、直接轉發(Direct exchange)

上一篇的日誌系統廣播所有的消息給所有的消費者。

現在想:可能希望把致命類型的錯誤寫入硬盤,而不把硬盤空間浪費在警告或者消息類型的日誌上。

之前我們使用fanout類型的轉發器,但是並沒有給我們帶來更多的靈活性:僅僅可以愚蠢的轉發。

我們將會使用direct類型的轉發器進行替代。direct類型的轉發器背後的路由轉發算法很簡單:

​ 消息會被推送至綁定鍵(binding key)和消息發佈附帶的選擇鍵(routing key)完全匹配的隊列。

圖解:

img

我們可以看到direct類型的轉發器與兩個隊列綁定。

第一個隊列與綁定鍵orange綁定

第二個隊列與轉發器間有兩個綁定,一個與綁定鍵black綁定,另一個與green綁定鍵綁定。
這樣的話,當一個消息附帶一個選擇鍵(routing key) orange發佈至轉發器將會被導向到隊列Q1。消息附帶一個選擇鍵(routing key)black或者green將會被導向到Q2.所有的其他的消息將會被丟棄。

3、多重綁定(multiple bindings)

img

使用一個綁定鍵(binding key)綁定多個隊列是完全合法的。如上圖,一個附帶選擇鍵(routing key)的消息將會被轉發到Q1和Q2。

4、發送日誌(Emittinglogs)

我們將消息發送到direct類型的轉發器而不是fanout類型。我們將把日誌的嚴重性作爲選擇鍵(routing key)。這樣的話,接收程序可以根據嚴重性來選擇接收。我們首先關注發送日誌的代碼:

像以前一樣,我們需要先創建一個轉發器:

channel.exchangeDeclare(EXCHANGE_NAME,"direct");

然後我們準備發送一條消息:

channel.basicPublish(EXCHANGE_NAME,severity, null, message.getBytes());

爲了簡化代碼,我們假定‘severity’是‘info’,‘warning’,‘error’中的一個。

5、訂閱

接收消息的代碼和前面的博客的中類似,只有一點不同:我們給我們所感興趣的嚴重性類型的日誌創建一個綁定。

StringqueueName = channel.queueDeclare().getQueue();
for(Stringseverity : argv){
    channel.queueBind(queueName, EXCHANGE_NAME, severity);
}

6、完整的實例

1.創建發送者

public class EmitLogDirect
{

    private static final String EXCHANGE_NAME = "ex_logs_direct";
    private static final String[] SEVERITIES = { "info", "warning", "error" };

    public static void main(String[] argv) throws java.io.IOException, TimeoutException {
        // 創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建頻道
        Channel channel = connection.createChannel();
        // 聲明轉發器的類型
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        //發送6條消息
        for (int i = 0; i < 6; i++)
        {
            String severity = getSeverity();
            String message = severity + "_log :" + UUID.randomUUID().toString();
            // 發佈消息至轉發器,指定routingkey
            channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }

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

    /**
     * 隨機產生一種日誌類型
     *
     * @return
     */
    private static String getSeverity()
    {
        Random random = new Random();
        int ranVal = random.nextInt(3);
        return SEVERITIES[ranVal];
    }
}

2.創建接受者

public class ReceiveLogsDirect
{

    private static final String EXCHANGE_NAME = "ex_logs_direct";
    private static final String[] SEVERITIES = { "info", "warning", "error" };

    public static void main(String[] argv) throws java.io.IOException,
            java.lang.InterruptedException, TimeoutException {
        // 創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置ip
        factory.setHost("localhost");
        //創建連接
        Connection connection = factory.newConnection();
        //創建頻道
        Channel channel = connection.createChannel();
        // 聲明direct類型轉發器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        // 創建一個非持久的、唯一的且自動刪除的隊列,臨時隊列
        String queueName = channel.queueDeclare().getQueue();

        String severity = getSeverity();

        // 指定binding_key
        channel.queueBind(queueName, EXCHANGE_NAME, severity);
        System.out.println(" [*] Waiting for "+severity+" logs. To exit press CTRL+C");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);

        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());

            System.out.println(" [x] Received '" + message + "'");
        }
    }

    /**
     * 隨機產生一種日誌類型
     *
     * @return
     */
    private static String getSeverity()
    {
        Random random = new Random();
        int ranVal = random.nextInt(3);
        return SEVERITIES[ranVal];
    }
}

3.總結:

發送消息時可以設置routing_key,接收隊列與轉發器間可以設置binding_key,接收者接收與binding_key與routing_key相同的消息。

五:主題

1、 主題轉發(Topic Exchange)

發往主題類型的轉發器的消息不能隨意的設置選擇鍵(routing_key),必須是由點隔開的一系列的標識符組成。標識符可以是任何東西,但是一般都與消息的某些特性相關。一些合法的選擇鍵的例子:”stock.usd.nyse”, “nyse.vmw”,”quick.orange.rabbit”.你可以定義任何數量的標識符,上限爲255個字節。

2.

3.完整例子

1.發送

public class EmitLogTopic
{

    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();

        //指定topic的轉發器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        String[] routing_keys = new String[] { "kernal.info", "cron.warning","auth.info", "kernel.critical" };

        for (String routing_key : routing_keys)
        {
            String msg = UUID.randomUUID().toString();
            channel.basicPublish(EXCHANGE_NAME, routing_key, null, msg.getBytes());
            System.out.println(" [x] Sent routingKey = "+routing_key+" ,msg = " + msg + ".");
        }

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

2.接收1

public class ReceiveLogsTopicForCritical
{

    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();
        // 聲明topic轉發器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        // 隨機生成一個隊列
        String queueName = channel.queueDeclare().getQueue();

        // 接收所有與kernel相關的消息
        channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);

        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            String routingKey = delivery.getEnvelope().getRoutingKey();

            System.out.println(" [x] Received routingKey = " + routingKey  + ",msg = " + message + ".");
        }
    }
}

2.接收2

public class ReceiveLogsTopicForKernel
{

    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();
        // 聲明topic轉發器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        // 隨機生成一個隊列
        String queueName = channel.queueDeclare().getQueue();

        //接收所有與kernel相關的消息
        channel.queueBind(queueName, EXCHANGE_NAME, "kernel.*");

        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, true, consumer);

        while (true)
        {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            String routingKey = delivery.getEnvelope().getRoutingKey();

            System.out.println(" [x] Received routingKey = " + routingKey + ",msg = " + message + ".");
        }
    }
}

六:參考

http://blog.csdn.net/lmj623565791/article/details/37706355

http://blog.csdn.net/lmj623565791/article/details/37669573

http://blog.csdn.net/lmj623565791/article/details/37657225

http://blog.csdn.net/lmj623565791/article/details/37620057

http://blog.csdn.net/lmj623565791/article/details/37607165

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章