RabbitMQ 使用 | 第四篇:路由选择
大部分内容仅仅对官方教程进行了翻译,有些内容为了更简便进行了调整
上一篇说发布/订阅模型的时候讲到了四中Exchange
:fanout,direct,topic,header
,并且使用了fanout
实现了发布订阅模式,在发布/订阅模式中所有的消息都会被订阅者接受。这一节我们会修改发布者和订阅者的代码达到消息过滤
的功能,使用的是direct Exchange
Direct Exchange
在开始之前,我们先介绍direct
这种Exchange
,这种类型的算法很简单,会将消息转发到绑定健
和消息发布指定的选择键
完全匹配的队列,简单来说,就是我们使用这种Exchange
之后,在发送消息的时候可以指定一个key
,然后接受订阅也可以指定一个key,如果这两个key相同,消息就会发送到订阅者哪里。
解释绑定健
和选择键
绑定键
首先回顾一下上一节绑定的代码:
channel.queueBind(queueName,EXCHANGE_NAME,"")
绑定表示Exchange和队列之间的关系,我们可以简单的认为:队列对该
Exchange
的消息感兴趣。注意一下第三个参数,我们称之为’绑定健’,这个健依赖
Exchange
的类型,对于fanout
类型,这个值没有意义。
一个队列可以绑定多个绑定健
选择键
回顾一下上一节发送消息的代码:
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
这里的第二个参数就是
选择键
实现路由
嗯。理解了绑定键
和选择键
之后,我们可以得出发送端代码:
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//...
String selectKey = "error";
channel.basicPublish(EXCHANGE_NAME, selectKey, null, message.getBytes());
接收端的代码变成了:
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
String bindKey = "error";
channel.queueBind(queueName, EXCHANGE_NAME, bindKey);
下面是完整的代码:
public class Publisher {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv)
throws java.io.IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String[] messages = new String[]{"First message.", "Second message..",
"Third message...", "Fourth message....", "Fifth message....."};
String selectKey = "error";
for (String message : messages) {
channel.basicPublish(EXCHANGE_NAME, selectKey, null, message.getBytes());
System.out.println("[x] Sent '" + message + "'");
}
channel.close();
connection.close();
}
}
public class Subscriber {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
String bindKey = "error";
channel.queueBind(queueName, EXCHANGE_NAME, bindKey);
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 '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}
在这里可能会出点问题:因为这里是使用logs
作为名称,但是上一篇的例子中已经使用了logs
名称了,他的Exchange
的类型是fanout
,RabbitMQ
不允许修改定义。运行会报错,解决办法如下:
打开终端执行以下命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
控制台输出:
//publisher
[x] Sent 'First message.'
[x] Sent 'Second message..'
[x] Sent 'Third message...'
[x] Sent 'Fourth message....'
[x] Sent 'Fifth message.....'
Process finished with exit code 0
//subscriber
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'First message.'
[x] Received 'Second message..'
[x] Received 'Third message...'
[x] Received 'Fourth message....'
[x] Received 'Fifth message.....'
发送接收没毛病。但是还没有展现出过滤的功能。我们先把接收端的程序停止。
然后在接收端添加一行代码:
String bindKey = "error";
System.out.println("接收端启动:我感兴趣的内容是:" + bindKey);
启动.然后修改接收端:
String bindKey = "info";
再启动多一个接收端,此时我们有两个接收端在运行,一个对error
感兴趣,一个对info
感兴趣。
这个时候我们启动发送端,查看3个控制台:
//Publish
[x] Sent 'First message.'
[x] Sent 'Second message..'
[x] Sent 'Third message...'
[x] Sent 'Fourth message....'
[x] Sent 'Fifth message.....'
Disconnected from the target VM, address: '127.0.0.1:63461', transport: 'socket'
Process finished with exit code 0
//Subscriber
[*] Waiting for messages. To exit press CTRL+C
接收端启动:我感兴趣的内容是:error
[x] Received 'First message.'
[x] Received 'Second message..'
[x] Received 'Third message...'
[x] Received 'Fourth message....'
[x] Received 'Fifth message.....'
//Subscriber
[*] Waiting for messages. To exit press CTRL+C
接收端启动:我感兴趣的内容是:info
根据控制台输出,可以确定消息只发送到了bingKey
为error
的订阅者哪里。
接着我们把发送端的selectKey
改为info
之后,控制台输出:
//Publish
[x] Sent 'First message.'
[x] Sent 'Second message..'
[x] Sent 'Third message...'
[x] Sent 'Fourth message....'
[x] Sent 'Fifth message.....'
Disconnected from the target VM, address: '127.0.0.1:63461', transport: 'socket'
Process finished with exit code 0
//Subscriber
[*] Waiting for messages. To exit press CTRL+C
接收端启动:我感兴趣的内容是:error
[x] Received 'First message.'
[x] Received 'Second message..'
[x] Received 'Third message...'
[x] Received 'Fourth message....'
[x] Received 'Fifth message.....'
//Subscriber
[*] Waiting for messages. To exit press CTRL+C
接收端启动:我感兴趣的内容是:info
[x] Received 'First message.'
[x] Received 'Second message..'
[x] Received 'Third message...'
[x] Received 'Fourth message....'
[x] Received 'Fifth message.....'
可以看到,这次启动仅仅bingKey
为info
的订阅者收到消息。
这里介绍了Exchange
类型为direct
的使用,为了省事,这里顺便把 topic
也介绍了。
Topic主题
总的来说topic
和direct
的使用差不了多少,首先需要把发布端和接收端的Exchange
声明改为topic
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
然后发布消息的时候,同样是声明了一个选择键
不通之处在于接收端的绑定健
可以是用通配符匹配:
例如:
*.info
可以接收选择键
为a.info b.info
等等info.*
可以接收选择键
为info.a
info.b
等等