系統拆分解耦利器之消息隊列---RabbitMQ-主題(Topic)

[一曲廣陵不如晨鐘暮鼓]

本文,我們來介紹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爲“quick.orange.rabbit”,其將會被投遞到兩個隊列中。如果發送的消息使用的binding key爲"lazy.orange.elephant"同樣將會被投遞到兩個隊列當中。但是如果發送的消息使用的binding key爲“quick.orange.fox”將會只投遞到Q1的隊列中。同理,如果發送的消息使用的binding key爲“lazy.brown.fox”,那其就只會被投遞到Q2當中。特別的,如果消息使用的binding key爲“lazy.pink.rabbit”,正如上面我們介紹的“#”的規則定義,其將會被投遞到Q2隊列當中,並且只會被投遞一次。最後,像前面的direct一樣,如果消息的binding key沒有與之配置的隊列,如“quick.brown,fox”,其將會被丟棄掉。
特別的,如果發送了一個只包含一個單詞的binding key或者單詞數量更多的binding key時,又作如何處理呢?如:“orange”,“quick.orange.male.rabbit”。如果是這樣的話,由於其沒有匹配到任何規則,所以都會被丟棄掉。另一方面,如果匹配到了,如“lazy.orange.male.rabbit”,即使其包含有4個單詞,它也是能夠被投遞到Q2當中去的。
特別備註:
RabbitMQ的Topic模式的功能是非常強大的。它能夠表現成爲其他任何一個類型的exchange。
  • 當一個隊列的binding key爲“#”時,其能夠接收到所有的消息,就像routing key不存在一樣。
  • 當一個隊列的binding key中不使用“#”或者“*”時,其表現出的特性與direct類型表現的特性完全一致。
綜上所述,我們來看看完整的工程吧,結構圖如下:

1.修改pom文件,具體內容請看前文,在此不再贅述。
2.創建EmitLogTopic文件,具體內容如下:
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

發佈了119 篇原創文章 · 獲贊 182 · 訪問量 59萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章