本机将会介绍一个新的exchange:direct交换机,通过程序示例演示其消息routing(路由)的功能。
本机我们将通过一个发送日志的程序来演示direct类型的exchange的routing功能。生产者发送日志,级别分别为info,warn,error等,消费者只会接收routing key 跟其队列的binding key完全匹配的消息。
channel.queueBind(queueName, EXCHANGE_NAME, "");
在上一篇博客《(6)RabbitMQ之fanout交换机——Publish/Subscribe(发布订阅)》中我们已经用过上面的代码进行绑定。绑定是交换机和队列之间的关系。这可以简单地理解为:队列对来自此交换机的消息感兴趣。绑定可以采用额外的routingKey参数。为了避免与basic_publish参数混淆,我们将其称为 binding key。这就是我们如何使用键创建绑定:
channel.queueBind(queueName,EXCHANGE_NAME,“black”);
绑定键的意义依赖于转发器的类型。对于fanout类型,忽略此参数。
上节博客中使用fanout类型的交换机,只能够对消息进行转发,不够灵活,不能够对消息按照一定的规则进行转发,而direct类型的交换机则可以做到这一点。direct类型的交换机背后的路由转发算法很简单:消息会被推送至binding key和消息routing key完全匹配的队列。图解:
上图,我们可以看到direct类型的转发器与两个队列绑定。第一个队列与绑定键orange绑定,第二个队列与转发器间有两个绑定,一个与绑定键black绑定,另一个与green绑定键绑定。
这样的话,当一个消息附带一个选择键(routing key) orange发布至转发器将会被导向到队列Q1。消息附带一个选择键(routing key)black或者green将会被导向到Q2,所有的其他的消息将会被丢弃。
使用相同的binding key绑定多个队列是完全合法的。在我们的示例中,我们可以在X和Q1之间添加绑定键black绑定。在这种情况下,direct交换机将表现得想fanout交换机一样,并将消息广播到所有匹配的队列。路由键为black的消息将传送到Q1和Q2。
我们通过消费者发送info,warn,error的日志,发送的日志级别(也就是routing key)是随机的。消费者有两个,消费者1只接收info级别的日志,消费者2只接收warn,error的日志。
注意:这里消费者1和2是两个不同的队列,消费者2有两个binding key,可以通过多次调用channel.queueBind()来添加多个binding key,如下所示:
channel.queueBind(queueName,EXCHANGE_NAME,"warn");
channel.queueBind(queueName,EXCHANGE_NAME,"error");
消息生产者代码如下:项目GitHub地址 https://github.com/RookieMember/RabbitMQ-Learning.git。
package cn.wkp.rabbitmq.newest.exchange.direct;
import java.util.Random;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import cn.wkp.rabbitmq.util.ConnectionUtil;
public class Send {
private final static String EXCHANGE_NAME = "direct_exchange";
private static String[] logLevels={"info","warn","error"};
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
// 从连接中创建通道
Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
Random random = new Random();
// 消息内容
for(int i=0;i<10;i++){
//随机得到路由键
String routingKey = logLevels[random.nextInt(3)];
String message = "日志序号i:"+i+",级别:"+routingKey;
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
System.out.println("Sent message:" + message);
}
//关闭通道和连接
channel.close();
connection.close();
}
}
消费者1代码如下:
package cn.wkp.rabbitmq.newest.exchange.direct;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import cn.wkp.rabbitmq.util.ConnectionUtil;
public class Recv1 {
private final static String EXCHANGE_NAME = "direct_exchange";
private final static String QUEUE_NAME = "direct_queue1";
private final static String BINDING_KEY = "info";
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, BINDING_KEY);
// 指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
channel.basicQos(1);
// 定义队列的消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1收到消息:" + new String(body));
// 消费者手动发送ack应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 监听队列
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2代码如下:
package cn.wkp.rabbitmq.newest.exchange.direct;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import cn.wkp.rabbitmq.util.ConnectionUtil;
public class Recv2 {
private final static String EXCHANGE_NAME = "direct_exchange";
private final static String QUEUE_NAME = "direct_queue2";
private final static String[] BINDING_KEYS = {"warn","error"};
public static void main(String[] argv) throws Exception {
// 获取到连接以及mq通道
Connection connection = ConnectionUtil.getConnection();
final Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定队列到交换机
for (String bindingKey : BINDING_KEYS) {
//循环添加了多个binding key
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, bindingKey);
}
// 指该消费者在接收到队列里的消息但没有返回确认结果之前,它不会将新的消息分发给它。
channel.basicQos(1);
// 定义队列的消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2收到消息:" + new String(body));
// 消费者手动发送ack应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 监听队列
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
运行结果如下(由于routing key是随机的,所以可能每次结果都不一样):
Sent message:日志序号i:0,级别:warn
Sent message:日志序号i:1,级别:error
Sent message:日志序号i:2,级别:warn
Sent message:日志序号i:3,级别:error
Sent message:日志序号i:4,级别:info
Sent message:日志序号i:5,级别:warn
Sent message:日志序号i:6,级别:info
Sent message:日志序号i:7,级别:error
Sent message:日志序号i:8,级别:warn
Sent message:日志序号i:9,级别:warn
消费者1收到消息:日志序号i:4,级别:info
消费者1收到消息:日志序号i:6,级别:info
消费者2收到消息:日志序号i:0,级别:warn
消费者2收到消息:日志序号i:1,级别:error
消费者2收到消息:日志序号i:2,级别:warn
消费者2收到消息:日志序号i:3,级别:error
消费者2收到消息:日志序号i:5,级别:warn
消费者2收到消息:日志序号i:7,级别:error
消费者2收到消息:日志序号i:8,级别:warn
消费者2收到消息:日志序号i:9,级别:warn
通过控制台输出可以看到已经达到了我们想要的结果,下一节将会介绍另一种topic类型的交换机。