[一曲廣陵不如晨鐘暮鼓]
本文,我們來介紹RabbitMQ中的Topic類型的exchange。在正式開始之前,我們假設RabbitMQ服務已經啓動,運行端口爲5672,如果各位看官有更改過默認配置,那麼就需要修改爲對應端口,保持一致即可。
準備工作:
操作系統:window 7 x64
其他軟件:eclipse mars,jdk7,maven 3
--------------------------------------------------------------------------------------------------------------------------------------------------------
主題(Topic)
在上文中,我們已經使用點對點的direct方式替換掉廣播式的fanout模式,從而改進了日誌系統。儘管這種點對點的direct方式已經成功的提升了系統的靈活性,但是其還是有限制的。那就是其不能基於多參數的路由。
在我們構建的日誌系統中,我們想要訂閱的方式不只是基於日誌級別,並且還需要按照日誌的生產源進行訂閱。你可能從unix日誌工具:syslog當中已經瞭解到這個改變,這種路由方式同時基於日誌的級別和產生日誌的生產源(auth/cron/kern)。
通過這種方式,能夠給我們收集系統日誌提供極大的靈活性。舉個例子:比如我們想監聽的只是非常嚴重的錯誤,並且錯誤源是“cron”,在這個基礎之上,我們還需要收集來自“kern”的所有日誌。
爲了實現上面的這個需求,下面來介紹一下RabbitMQ中更加複雜的toipc類型的exchange的概念及使用方法吧。
主題類型的交換器(Topic exchange)
在向topic類型的exchange發送消息時,不能夠隨意的設定routing_key。其必須是一個通過句號分割的單詞的列表。至於單詞,可以是任意的有意義的或者無意義的均,但通常情況下,這個單詞都暗示了消息的某些特性。舉個例子:“stockl.usd.nyse”,“nyse.vmw”,“quick.orange.rabbit”等等。單詞格式不做任何的限制,但是其長度最多只能有255個字節(byte)。
同時,binding key必須是與消息發送者使用的binding key的格式保持一致。這裏topic後臺處理邏輯和前文我們介紹的點對點的direct處理邏輯非常類似。一個帶有特定binding key的消息將會被投遞到所有的與之匹配的隊列當中。但是,請注意以下兩個特殊符號:
- *(star):只能匹配一個單詞。(0個或者多個都不可以)
- #(hash):能夠匹配大於等於零個單詞。
這個概念可以很容易的使用下面這張圖進行解釋:
在這幅圖中,我們將會發送能夠描述動物特性的消息。消息將帶有一個routing key(有兩個間隔點)。第一個單詞用以描述速度。第二個單詞用以描述顏色。第三個單詞用以描述種類。合起來的格式就是:“<speed>.<colour>.<species>”。
我們創建三個綁定關係:
- Q1:【*orange.*】
- Q2:【*.*.rabbit】,【lazy.#】
- 當一個隊列的binding key爲“#”時,其能夠接收到所有的消息,就像routing key不存在一樣。
- 當一個隊列的binding key中不使用“#”或者“*”時,其表現出的特性與direct類型表現的特性完全一致。
package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class EmitLogTopic {
// 隊列名稱
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException {
// 創建連接和頻道
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 聲明隊列
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String[] routing_keys = new String[] { "kernel.info", "cron.warning", "auth.info", "kernel.critical" };
for (String routing_key : routing_keys) {
String message = UUID.randomUUID().toString();
channel.basicPublish(EXCHANGE_NAME, routing_key, null, message.getBytes());
System.out.println("[x] Sent routing_key:" + routing_key + ",message:" + message);
}
// 關閉頻道和資源
channel.close();
connection.close();
}
}
3.創建ReceiveLogsTopicForCritical文件,具體內容如下:package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Random;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
public class ReceiveLogsTopicForCritical {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args)
throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");
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());
String routingkey = delivery.getEnvelope().getRoutingKey();
System.out.println("[x] Received routingKey:" +routingkey+",message:"+ message);
}
}
}
4.創建ReceiveLogsTopicForKernel文件,具體內容如下:package com.csdn.ingo.rabbitmq_1;
import java.io.IOException;
import java.util.Random;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
public class ReceiveLogsTopicForKernel {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args)
throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "kernel.*");
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());
String routingkey = delivery.getEnvelope().getRoutingKey();
System.out.println("[x] Received routingKey:" +routingkey+",message:"+ message);
}
}
}
5.測試方法:首先運行兩個接收方程序,在運行發送者程序,觀察控制檯輸出即可。-------------------------------------------------------------------------------------------------------------------------------------------------------
至此,系統拆分解耦利器之消息隊列---RabbitMQ-主題(Topic) 結束
參考資料:
官方文檔:http://www.rabbitmq.com/tutorials/tutorial-five-java.html