本例在前一例的基础上,通过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监听处理。