在上篇文章Routing中,我們改進了日誌系統,使用類型爲direct的exchange,使得可以有選擇性的接收日誌。而不是fanout那樣只是簡單的廣播信息。
雖然使用了direct的exchange來改進我們的日誌系統,但是還是有侷限性,無法根據多重條件來進行路由選擇。
在我們的日誌系統中,我們希望不僅僅根據日誌的級別,而且根據日誌的來源來訂閱日誌。類似於syslog這個unix工具,它根據嚴重性(info/warn/crit...)和設備(auth/cron/kern...)來選擇發送日誌。
這樣可能給予我們更多的靈活性,我們希望關注來自於“cron”的critical errors和來自於“kern”的所有日誌。
發往topic類型exchange的消息不能任意選擇routing_key,必須是由.隔開的一系列標識,這些標識可以是任何東西,但經常是一些與消息特性相關的詞,一些合法的routing key的例子:"stock.usd.nyse","nyse.vmw","quick.orange.rabbit"。routing key的長度限制爲255bytes。
綁定的key和routing key的形式一樣,topic exchange的邏輯跟direct exchange類似。附帶特殊routing key的消息會被分發到所有綁定匹配的binding key的queue中。需要注意的是,關於binding key有兩個特殊的情況。
* 可以匹配一個標識。
#可以匹配0個或多個標識。
如上圖所示,我們準備發送關於動物的消息,消息會附帶一個routing key包含3個標識(兩個.隔開),第一個標識描述速度,第二個標識描述顏色,第三個標識描述種類:"<speed>.<colour>.<species>"。
我們建了3個綁定關係。Q1通過"*.orange.*"綁定,Q2通過"*.*.rabbit"和"lazy.#"來綁定。
這幾個綁定關係可以總結爲:
Q1對所有橙色動物感興趣。
Q2想要知道關於兔子的一切以及關於懶洋洋的動物的一切
消息附帶routing key爲"quick.orange.rabbit"將會被分發到Q1和Q2。消息附帶routing key爲"lazy.orange.elephant"也會被分發到Q1和Q2。消息附帶routing key爲"quick.orange.fox"只會被分發到Q1。消息附帶routing key爲"lazy.brown.fox"會被分發到Q2。消息附帶routing key爲"lazy.pink.rabbit"將會被分發一次到Q2,雖然這個消息匹配到兩個binding key。消息附帶routing key爲"quick.brown.fox"將會被丟棄。
如果我們違法我們的約定,發送一個或者四個標識符的選擇鍵,類似:orange,quick.orange.male.rabbit,這些選擇鍵不能與任何綁定鍵匹配,所以消息將會被丟棄。
代碼如下:EmitLogTopic.java
package org.rabbitmq;
import com.rabbitmq.client.BuiltinExchangeType;
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) {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5673);
connection = factory.newConnection();
channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String regArgv[] = new String[]{"kern.critical","A critical kernel error"};
String routingKey = getRouting(regArgv);
String message = getMessage(regArgv);
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (Exception ignore) {
}
}
}
}
private static String getRouting(String[] strings) {
if (strings.length < 1)
return "anonymous.info";
return strings[0];
}
private static String getMessage(String[] strings) {
if (strings.length < 2)
return "Hello World!";
return joinStrings(strings, " ", 1);
}
private static String joinStrings(String[] strings, String delimiter, int startIndex) {
int length = strings.length;
if (length == 0)
return "";
if (length < startIndex)
return "";
StringBuilder words = new StringBuilder(strings[startIndex]);
for (int i = startIndex + 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}
ReceiveLogsTopic.java
package org.rabbitmq;
import java.io.IOException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
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");
factory.setPort(5673);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = channel.queueDeclare().getQueue();
String regArgv[] = new String[]{"kern.*","*.critical"};
if (regArgv.length < 1) {
System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
System.exit(1);
}
for (String bindingKey : regArgv) {
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
}
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}
運行結果如下:
參考:http://www.rabbitmq.com/tutorials/tutorial-five-java.html