RabbitMQ文檔翻譯三(JAVA).發佈/訂閱

上一個教程中,我們創建了一個工作隊列。工作隊列背後的假設是每個任務只傳遞給一個工人。在這一部分中,我們將做一些完全不同的事情——我們將向多個消費者傳遞一個消息。這種模式被稱爲“發佈/訂閱”。

爲了說明這種模式,我們將構建一個簡單的日誌系統。它將由兩個程序組成——第一個程序將發出日誌消息,第二個程序將接收並打印它們。

在我們的日誌系統中,接收器程序的每個運行副本都會收到消息。這樣,我們就可以運行一個接收器並將日誌定向到磁盤;同時,我們還可以運行另一個接收器並在屏幕上查看日誌。

本質上,發佈的日誌消息將被廣播到所有接收者。

交換機

在本教程的前面部分中,我們向隊列發送和接收消息。現在是時候在Rabbit中引入完整的消息傳遞模型了。
讓我們快速回顧一下之前教程中介紹的內容:

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

RabbitMQ中消息傳遞模型的核心思想是,生產者從不直接向隊列發送任何消息。實際上,生產者常常根本不知道消息是否會被傳遞到任何隊列。

相反,生產者只能向交換機發送消息。交換機是一個很簡單的東西。一方面它接收來自生產者的消息,另一方面它將消息推送到隊列中。交換機必須確切知道如何處理它接收到的消息。是否應該將其附加到特定隊列?是否應該將其附加到多個隊列中?或者應該被丟棄。其規則由交換機類型定義。
在這裏插入圖片描述
有幾種交換機類型可用:direct、topic、headers和fanout。我們將集中討論最後一個——fanout扇形。讓我們創建一個這種類型的交換,並將其稱爲logs:

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

扇型交換機非常簡單。正如您可能從名稱中猜到的,它只是將它接收到的所有消息廣播到它知道的所有隊列。這正是我們需要的日誌記錄器。

列出所有交換機
要列出服務器上的交換,可以運行非常有用的rabbitmqctl:

  • sudo rabbitmqctl list_exchanges**

在此列表中,將有一些amq.*交換和默認(未命名)交換。這些都是默認創建的,但現在不太可能需要使用它們。
無名交換機
在本教程的前幾部分中,我們對交換一無所知,但仍然能夠向隊列發送消息。這是可能的,因爲我們使用的是默認的交換,我們用空字符串(“”)標識它。
回想一下我們之前是如何發佈消息的:

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

第一個參數是交換的名稱。空字符串表示默認或無名稱的交換機:消息被路由到具有routingKey指定的名稱的隊列(如果存在的話)。

現在,我們可以發佈到指定的交易所:

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

臨時隊列

您可能還記得,我們使用的隊列具有特定的名稱(還記得hello和task_queue嗎?)。能夠命名隊列對我們來說至關重要——我們需要將消費者指向同一個隊列。當您想在生產者和消費者之間共享隊列時,給隊列一個名稱非常重要。

但我們的日誌系統不是這樣。我們希望瞭解所有日誌消息,而不僅僅是其中的一個子集。我們也只對當前的消息感興趣,而不是舊的消息。要解決這個問題,我們需要兩件事。

首先,當我們連接到Rabbit時,我們需要一個新的、空的隊列。爲此,我們可以創建一個隨機名稱的隊列,或者更好的方法是讓服務器爲我們選擇一個隨機隊列名稱。

其次,一旦我們斷開消費者的連接,隊列就會自動刪除。

在Java客戶端中,當我們不向queueDeclare()提供參數時,我們將創建一個非持久的、排他的、自動刪除的隊列,並生成一個名稱:

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

您可以在隊列指南中瞭解有關獨佔標誌和其他隊列屬性的更多信息。

此時,queueName包含一個隨機隊列名。例如它看起來像amq.gen-JzTY20BRgKO-HjmUJj0wLg。

綁定

在這裏插入圖片描述

我們已經創建了扇型交換機和隊列。現在我們需要告訴交換機向我們的隊列發送消息。交換和隊列之間的關係稱爲綁定。

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

從現在起,logs交換機將向我們的隊列追加消息。

列出綁定
你可以列出正在使用的綁定關係,你猜到的,

  • rabbitmqctl list_bindings

把它們放在一起

在這裏插入圖片描述

發送日誌消息的生產者程序,與上一個教程的日誌看起來沒什麼不同。最重要的變化是,我們現在希望將消息發佈到logs交換機,而不是匿名的交換機。我們需要在發送時提供routingKey,但它的值在扇型交換中被忽略。代碼在這裏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 + "'");
    }
  }
}

如您所見,在建立連接之後,我們聲明瞭交換機。此步驟是必需的,因爲禁止發佈消息到不存在的交換機。

如果還沒有隊列綁定到交換機,那麼消息將丟失;如果沒有消費者在監聽,我們可以安全地丟棄消息。

代碼ReceiveLogs.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 -> { });
  }
}

像以前一樣編譯

javac -cp $CP EmitLog.java ReceiveLogs.java

如果要將日誌保存到文件中,只需打開控制檯並鍵入:

java -cp $CP ReceiveLogs > logs_from_rabbit.log

如果希望在屏幕上查看日誌,請打開一個新的終端並運行:

java -cp $CP ReceiveLogs

當然,要發送日誌消息:

java -cp $CP EmitLog

使用rabbitmqctl list_bindings,您可以驗證代碼是否實際創建了所需的綁定和隊列。如果有兩個ReceiveLogs.java正在運行的程序應該會看到如下內容:
sudo rabbitmqctl list_綁定

sudo rabbitmqctl list_bindings
# => Listing bindings ...
# => logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
# => logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
# => ...done.

對結果的解釋很簡單:來自logs交換機的數據通過服務器分配的名稱進入兩個隊列。這正是我們想要的。
要了解如何監聽消息的子集,讓我們繼續學習教程4

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