本例在前一例的基礎上,通過Routing過濾消息,只接收感興趣的message。
基本概念
- binding: A binding is a relationship between an exchange and a queue.
- binding key: exchange和queue綁定時,用於指定哪些消息要被放到該queue中。
- routing key: 發佈的消息的路由,用於和binding key匹配,決定放入哪個queue.
- 多重綁定: 不同的queue可以使用相同的binding
key來綁定到同一個exchange;效果和fanout類型的exchange相同。
Tutorial 4 - 消息路由routing
(1) Producer
本例Producer部分,隨機生成severity=info, warning或者error的log(message),創建名爲“direct_logs”的exchange,併發布routing key爲severity的消息到RabbitMQ server,服務器收到消息之後就把消息丟給exchange,後面就由exchange決定消息被丟棄還是被放到某個queue中。
//MyMessage.java
public class MyMessage {
private String severity;
private String message;
public MyMessage(){
this.severity = "info";
this.message = "this is an info message";
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
//MyTask.java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.Random;
//producer發送消息給exchange,exchange決定把消息放到哪個queue裏面
public class MyTask implements Runnable {
//message index,用來查看多個consumer同時從broker取message時,消息是怎麼被分派的
private static Integer index = 0;
//define name of the exchange
private static final String EXCHANGE_NAME = "direct_logs";
//connection to the server(broker)
private Connection rbtMqConn;
//channel
private Channel rbtMqChnl;
//控制停止線程
private boolean isStop = false;
public void setIsStop(boolean stop){
this.isStop = stop;
}
@Override
public void run() {
try{
//1.創建一個connection鏈接到RabbitMQ服務器(connection爲我們處理socket/協議/認證授權等)
ConnectionFactory factory = new ConnectionFactory();
//本例使用本機作爲服務器;Consumer也從這個broker接收消息,也可以使用其它主機,比如172.16.21.111
factory.setHost("localhost");
rbtMqConn = factory.newConnection();
//2.創建一個channel
rbtMqChnl = rbtMqConn.createChannel();
//3.聲明exchange,名字爲EXCHANGE_NAME,類型爲direct
//這一步必須,因爲不能發佈到不存在的exchange是不允許的
//通過rabbitmqctl list_exchanges -p <vhost>,查看vhost上的exchange
rbtMqChnl.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//如果沒有queue綁定到exchange上,這些消息將會丟失,但這對本例來說沒問題;
//如果沒有consumer正在監聽,我們可以放心地丟棄消息。
//send message per 3s
while (!isStop){
//隨機生成不同severity的log(message)
MyMessage myMessage = getMessage(index++);
//4.通過指定的exchange發佈routingKey爲severity(info/warning/error)的消息:
rbtMqChnl.basicPublish(EXCHANGE_NAME,
myMessage.getSeverity()/*routing key*/,
null,
myMessage.getMessage().getBytes("UTF-8"));
System.out.println(" [Producer] Sent '" + myMessage.getSeverity() + ":" + myMessage.getMessage() + "'");
Thread.sleep(3000);
}
//5.最後,使用完之後,關閉channel和connection;
rbtMqChnl.close();
rbtMqConn.close();
}catch(Exception ex){
System.out.println(ex.getMessage());
}
System.out.println(" [Producer] Send task stop");
}
/**
* 生成隨機消息用於發送給RabbitMQ服務器
* */
private static MyMessage getMessage(Integer index) {
MyMessage msg = new MyMessage();
StringBuilder dotMsg = new StringBuilder("");
dotMsg.append(String.format("[%d]", index));
Random rand = new Random();
Integer num = rand.nextInt(6) + 1;//[1, 6]
for(Integer i=1; i <= num; ++i){
dotMsg.append(".").append(i.toString());
}
msg.setMessage(dotMsg.toString());
int type = num % 3;
switch (type){
case 0:
msg.setSeverity("info");
break;
case 1:
msg.setSeverity("warning");
break;
case 2:
msg.setSeverity("error");
break;
}
return msg;
}
}
//MyProducer.java
public class MyProducer {
public static void main(String[] argv) throws Exception {
MyTask sendTask = new MyTask();
Thread thread = new Thread(sendTask);
thread.start();
//let the thread run 60 seconds
Thread.sleep(60000);
sendTask.setIsStop(true);
}
}
(2) Consumer
本例Consumer部分,生成了一個隨機queue,然後分別使用不同的bindingkey(一個爲warning,另一個爲error)綁定到名爲“direct_logs”的exchange,監聽符合條件的message。
//MyConsumer.java
import com.rabbitmq.client.*;
import java.io.IOException;
public class MyConsumer
{
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
//1.創建connection鏈接到RabbitMQ Server
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");//使用本機的RabbitMQ Server
Connection connection = factory.newConnection();
//2.創建channel
Channel channel = connection.createChannel();
//3.聲明exchange,指定類型爲direct
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//4.使用隨機生成的消息隊列
//注意,當consumer退出連接時,該隨機隊列會自動刪除
String queueName = channel.queueDeclare().getQueue();
System.out.println(" [Consumer] declare to get random queue: " + queueName);
//5.queue綁定到exchange,並指定binding key(也可以稱作routing key)
//rabbitMQ接收消息之後給exchange,而exchange將根據該路由決定是否將消息放入該消息隊列
//routing key(binding key)的含義隨exchange的類型的不同而不同;
//對於fanout類型的exchange來說,routing key是沒有意義的,會被直接忽略。
//注意:支持多個綁定,見下面代碼
//使用rabbitmqctl list_bindings命令,查看綁定
//channel.queueBind(queueName, EXCHANGE_NAME, "info"/*bindingKey*/);
channel.queueBind(queueName, EXCHANGE_NAME, "warning"/*bindingKey*/);
channel.queueBind(queueName, EXCHANGE_NAME, "error"/*bindingKey*/);
//6.設置消息處理函數
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(" [Consumer] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
}
};
//7.監聽消息
channel.basicConsume(queueName, true, consumer);
}
}
(3) 運行結果
只有routing key = warning和error的message被consumer監聽處理。