在上一個教程中,我們改進了日誌記錄系統。我們沒有使用只能進行無腦廣播的扇形交換機,而是使用了直連交換機,並獲得了選擇性接收日誌的可能性。
雖然使用直接交換改進了我們的系統,但它仍然有侷限性-它不能基於多個標準進行路由。
在我們的日誌系統中,我們可能不僅要訂閱基於嚴重性的日誌,還需要基於發出日誌的源。您可能從 syslog unix tool中知道這個概念,該工具基於優先級(info/warn/crit…)和設備(auth/cron/kern…)路由日誌。
這將給我們很大的靈活性-我們可能只想監聽來自“cron”的錯誤日誌,也可以監聽“kern”的所有日誌。
爲了在我們的日誌系統中實現這一點,我們需要了解一個更復雜的主題交換機。
主題交換機
發送到主題交換機的消息不能有任意的路由鍵-它必須是由點分隔的單詞列表。單詞可以是任何內容,但通常它們指定與消息相關的一些特性。一些有效的路由鍵示例:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”. 路由鍵中可以有任意多個字,最多不超過255個字節。
綁定鍵的格式也必須相同。主題交換背後的邏輯類似於直接交換——使用特定路由鍵發送的消息將被傳遞到使用匹配綁定鍵綁定的所有隊列。但是,綁定密鑰有兩種重要的特殊情況:
*可以代替一個詞。
#可以代替零個或多個單詞。
用一個例子來解釋這一點最簡單:
在這個例子中,我們將發送所有描述動物的信息。消息將帶着路由鍵發送,路由鍵由三個單詞(兩個點)組成。路由鍵中的第一個詞將描述速度,第二個詞是顏色,第三個詞是“物種”:“”。
我們創建了三個綁定:Q1用綁定鍵“.orange.”綁定,Q2用“..rabbit”和“lazy.#。
這些綁定可以概括爲:
- Q1對所有橙色動物都感興趣。
- Q2想聽關於兔子的一切,以及關於懶惰動物的一切。
路由鍵設置爲“"quick.orange.rabbit”的消息將被傳送到兩個隊列。消息"lazy.orange.elephant"也會被髮送到兩個隊列。另一方面"quick.orange.fox"將只會被髮送到第一個隊列,"lazy.brown.fox"只到被投遞到第二個隊列。"lazy.pink.rabbit"將只傳遞到第二個隊列一次,即使它匹配兩個綁定快。"quick.brown.fox"與任何綁定都不匹配,因此將被丟棄。
如果我們違反約定的路由規則,用一個或四個單詞,比如"orange" 或 “quick.orange.male.rabbit”?好吧,這些消息將與任何綁定不匹配,並且將丟失。
另一方面"lazy.orange.male.rabbit",即使它有四個單詞,也將匹配最後一個綁定並將被傳遞到第二個隊列。
主題交換機
主題交換機功能強大,可以表現的像其他交換機一樣。
當隊列與“#”(哈希)綁定key綁定時,它將接收所有消息,而不管路由密鑰是什麼,就像扇形交換機一樣。
當綁定中不使用特殊字符“*”(星)和“#”(散列)時,主題交換將像直接交換一樣。
把它們放在一起
我們將在日誌系統中使用主題交換。我們首先假設日誌的路由鍵將有兩個詞:“”。
代碼與上一個教程中的代碼幾乎相同。
代碼EmitLogTopic.java:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
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");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String routingKey = getRouting(argv);
String message = getMessage(argv);
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
}
}
//..
}
代碼 ReceiveLogsTopic.java:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
public class ReceiveLogsTopic {
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();
if (argv.length < 1) {
System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
System.exit(1);
}
for (String bindingKey : argv) {
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
}
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 '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
}
}
編譯並運行示例,包括教程1中的類路徑-在Windows上,使用%CP%。
編譯:
javac -cp $CP ReceiveLogsTopic.java EmitLogTopic.java
要接收所有日誌:
java -cp $CP ReceiveLogsTopic "#"
要接收從設備“kern”來的所有日誌:
java -cp $CP ReceiveLogsTopic "kern.*"
或者,如果您只想瞭解“嚴重”日誌:
java -cp $CP ReceiveLogsTopic "*.critical"
可以創建多個綁定:
java-cp$cp ReceiveLogsTopic“kern.*”*.critical
併發出帶有路由鍵 “kern.critical” 類型的日誌消息:
java -cp $CP EmitLogTopic "kern.critical" "A critical kernel error"
祝你和這些程序玩得開心。請注意,代碼沒有對路由或綁定鍵進行任何假設(限制),你可以使用兩個以上的路由密鑰鍵。
接下來,在教程6中瞭解如何將往返消息作爲遠程過程調用