RabbitMQ 教程譯文(三) + 學習

原文地址
除了特殊聲明,以下所有圖片皆來自教程原文

發佈訂閱
在之前的教程中,我們創建了一個工作隊列。在例子中我們假設,每一個任務會發送給特定的一個消費者。在本章節中,我們要做完全不同的事:我們將會發送信息給多個消費者,這就是發佈訂閱模式。

爲了描述這個模式,我們將會創建一個簡單的日誌系統。這個系統包含兩部分內容,一是發出日誌信息,二是接收並打印日誌信息。

在我們的系統中,每一個運行的接收日誌的程序都會接收到日誌信息。這種情況下我們可能使用一個接收者將日誌直接存儲到硬盤,使用另一個接收者用於屏幕展示。

總的來說,日誌信息將會廣播給所有的接收者。

Exchanges
在之前的教程中,我們通過一個隊列來發送和接收信息。現在是時候介紹Rabbit中的完整信息模型了
我們先快速回憶下之前的教程內容
1、生產者就是一個發送信息的用戶應用
2、隊列就是存儲信息的緩存
3、消費者就是一個接收信息的用戶應用

在RabbitMQ中,消息模型的核心思想就是:生產者不會直接給隊列發送信息。甚至大部分情況是生產者根本不知道隊列的存在。

生產者只可以發送信息到一個exchangeexchange其實很簡單,一方面接收生產者發送的信息,一方面把信息加入到隊列中。exchange必須知道怎麼處理它接收到的信息。是要把它發送到指定隊列麼?還是發送到很多隊列?或者是不是應該丟棄。這些處理規則通過exchange類型來定義。
在這裏插入圖片描述
下面是一些有效的exchange類型:directtopicheadersfanout。我們重點看下最後一個類型,fanout。現在我們來創建這個類型的exchange,我們叫它“logs”。

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

fanout類型的exchange非常簡單,你可能已經根據名稱猜出它的用途了,該類型的exchange會廣播所有它接收到的信息給它知道的所有隊列。這正是我們日誌系統需要的。

展示Exchanges
你可以通過rabbitmqctl命令在服務端展示所有的exchange

sudo rabbitmqctl list_exchanges

在這個列表中,可能會有很多“amq.”的exchange和一些默認的exchange*,這些都是默認創建的exchange,目前你還不會用到。

未命名的exchange
在之前的教程中,我們一點都不知道exchange的存在,但是我們還是向隊列發送了信息。這是因爲我們之前使用了默認的exchange,我們是通過空字符串來定義的exchange。回憶下我們之前是怎麼發送信息的

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

這個方法的第一個參數就是exchange的名稱。空字符串表示這是一個默認或者無名的exchange。如果“routingKey”存在的話,信息會通過該值發送到指定隊列。

現在我們使用一個命名的exchange替換之前的

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

Temporary Queue臨時隊列
在我們之前的教程中,我們都會給隊列一個名稱(“hello”、“task_queue”)。對於我們來說給一個隊列命名是非常重要的,這樣我們就可以指定消費者消費哪個隊列。所以當你需要在生產者和消費者見共享隊列信息,給隊列命名是非常重要的。

但是,這些對我們日誌來說是不重要的。我們想獲取所有的日誌信息,而不是它的一部分。我們對於新的日誌感興趣而不是舊的日誌。爲了解決這個問題,我們需要做兩件事。

首先,無論何時我們連接到Rabbit,我們需要一個全新的空的隊列。爲了實現這個我們需要創建一個有隨機名稱的隊列,或者更好的情況是服務器給我們隊列一個隨機名稱。

其次,當消費者斷開連接,隊列要自動刪除。

在java實現中,我們提供了一個無參方法queueDeclare(),該方法會創建一個非持久,專一的(exclusive),自動刪除的隊列,該隊列的名稱是服務端生成的。

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

你可以在文檔中學習更多關於exclusive標識和其他參數的知識。

在上述代碼中,“queueName” 是一個隨機生成的名稱,它可能像這樣“amq.gen-JzTY20BRgKO-HjmUJj0wLg”。

Bindings綁定
在這裏插入圖片描述
我們已經創建了一個fanout類型的exchange和一個隊列,現在我們要讓exchange發送信息到隊列中。exchange和隊列中間的關係我們叫做綁定(Binding)。

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

從現在開始,“logs”exchange將會發送信息到隊列中。

Putting it all together 合體~
在這裏插入圖片描述
發送信息的生產者程序和之前教程中的看起來沒什麼不一樣。最大的改變就是,現在我們發送信息到“logs”exchange,而不是之前的無名exchange。在發送過程中我們需要提供routingKey,但是它對於fanout類型的exchange是無效的。下面就是全部的生產者程序“EmitLog.java”

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");
    try (Connection connection = factory.newConnection();
         Channel channel = connection.createChannel()) {
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        String message = argv.length < 1 ? "info: Hello World!" :
                            String.join(" ", argv);

        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    }
  }
}

正如你所見,我們在創建連接之後聲明瞭一個exchange,這一步很有必要,它避免了發送信息到一個不存在的exchange中。

如果沒有隊列綁定到exchange,那麼exchange中的信息將會丟失,但是這些對我們日誌系統沒什麼影響;如果還沒有消費者監聽隊列,那我們可以安全的丟棄這些信息。

“ReceiveLog.java”的代碼

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

public class ReceiveLogs {
  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");
    String queueName = channel.queueDeclare().getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, "");

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

    DeliverCallback deliverCallback = (consumerTag, delivery) -> {
        String message = new String(delivery.getBody(), "UTF-8");
        System.out.println(" [x] Received '" + message + "'");
    };
    channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
  }
}

後面就是運行查看結果

打完收工~~

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