第四节 Topic路由交换机:消息转发到关心话题的Queue
一、基本模型
P是生产者,是发消息的人。
X是topic类型的交换机。它会把消息的路由键取出来,与绑定它的队列做路由键匹配。如果队列关心的路由键能匹配上消息的路由键,则将这个消息投递到这个队列中。
Q1是一个队列,它是关心orange颜色的队列。
O2是一个队列,它是关心rabbit与lazy的队列。
C1是消费者,从Q1队列中获取消息。
C2是消费者,从Q2队列中获取消息。
- *(星号) * 可以替代一个单词。
- (hash) # 可以替换零个或多个单词
上图的基本模型希望表达的意思是,如果生产者发送了一个消息,其路由键是 Routing是 a.orange.a。路由器在收到这个消息后,会把这个消息的路由键与Q1和Q2关注的主题进行模糊匹配。因为*代表一个单词的意思,所以可以匹配到Q1队列。
如果消息的路由键是 lazy.a.b.c ,C2队列关心的主题是 lazy.#,#代表多个单词,所以可以匹配上这个消息的路由键。这个消息会被路由器转发到C2队列中。
Topic类型的交换机
任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上。使用的是一种正则匹配规则。生产者会发送一个带路由键的消息。Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。
Fanout类型的交换机
发送到exchange的所有消息会被转发到与exchange绑定的所有queue,不需要处理路由
Direct类型的交换机
需要处理路由键。该交换机收到消息后会把消息发送到接收指定routing-key的queue中。
二、使用代码
我们创建了一个Topic类型的交换机,然后在创建了两个队列。
第一个队列关心的主题是 *.orange.* ,* 代表一个单词的意思。如果消息的路由键能匹配的上这个主题,例如路由键是like.orange.color,那么交换机就会把这条消息转发到第一个队列中。
第二个队列关心的主题是 lazy.# ,#代表多个的意思。如果消息的路由键能匹配的上这个主题,例如路由键是lazy.body.girl,那么交换机就会把这条消息转发到第二个队列中。
发送端Producer.java
package com.safesoft.topicexchanger.mq04;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jay.zhou
* @date 2019/4/24
* @time 10:30
*/
public class Producer {
private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";
public static void main(String[] args) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String message = "topic交换机很有用";
//声明一个TOPIC类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//channel.basicPublish(EXCHANGE_NAME, "like.orange.color", null, message.getBytes("UTF-8"));
channel.basicPublish(EXCHANGE_NAME, "lazy.boy.girl", null, message.getBytes("UTF-8"));
LOGGER.info("消息发送成功:{}", message);
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
接收端Consumer01.java
package com.safesoft.topicexchanger.mq04;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* @author jay.zhou
* @date 2019/4/24
* @time 10:40
*/
public class Consumer01 {
private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";
private static final String QUEUE_NAME_01 = "local::mq04:queue:q01";
private static final String QUEUE_NAME_02 = "local::mq04:queue:q02";
public static void main(String[] args) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明一个TOPIC类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//申明两个个队列
/**
* 第一个参数是queue:要创建的队列名
* 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复消息
* 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
* 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
* 第五个参数是arguments:其它参数
*/
channel.queueDeclare(QUEUE_NAME_01, true, false, false, null);
channel.queueDeclare(QUEUE_NAME_02, true, false, false, null);
final String ROUTING_KEY_ORANGE = "*.orange.*";
final String ROUTING_KEY_LAZY = "lazy.#";
//队列一对ORANGE感兴趣,匹配 XXX.orange.XXX 的消息
channel.queueBind(QUEUE_NAME_01, EXCHANGE_NAME, ROUTING_KEY_ORANGE);
//队列二对LAZY感兴趣,匹配 lazy.XXX.XXX.XXX
channel.queueBind(QUEUE_NAME_02, EXCHANGE_NAME, ROUTING_KEY_LAZY);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
final String message = new String(body, "UTF-8");
LOGGER.info("队列一收到消息:{}", message);
}
};
//队列一确认消息
channel.basicConsume(QUEUE_NAME_01, true, consumer);
} catch (Exception e) {
LOGGER.error("an exception was occurred , caused by :{}", e.getMessage());
}
}
}
接收端Consumer02.java
package com.safesoft.topicexchanger.mq04;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* @author jay.zhou
* @date 2019/4/24
* @time 10:40
*/
public class Consumer02 {
private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
private static final String EXCHANGE_NAME = "local::mq04:exchange:e01";
private static final String QUEUE_NAME_01 = "local::mq04:queue:q01";
private static final String QUEUE_NAME_02 = "local::mq04:queue:q02";
public static void main(String[] args) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明一个TOPIC类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//申明两个个队列
/**
* 第一个参数是queue:要创建的队列名
* 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复消息
* 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
* 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
* 第五个参数是arguments:其它参数
*/
channel.queueDeclare(QUEUE_NAME_01, true, false, false, null);
channel.queueDeclare(QUEUE_NAME_02, true, false, false, null);
final String ROUTING_KEY_ORANGE = "*.orange.*";
final String ROUTING_KEY_LAZY = "lazy.#";
//队列一对ORANGE感兴趣,匹配 XXX.orange.XXX 的消息
channel.queueBind(QUEUE_NAME_01, EXCHANGE_NAME, ROUTING_KEY_ORANGE);
//队列二对LAZY感兴趣,匹配 lazy.XXX.XXX.XXX
channel.queueBind(QUEUE_NAME_02, EXCHANGE_NAME, ROUTING_KEY_LAZY);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
final String message = new String(body, "UTF-8");
LOGGER.info("队列二收到消息:{}", message);
}
};
//队列一确认消息
channel.basicConsume(QUEUE_NAME_02, true, consumer);
} catch (Exception e) {
LOGGER.error("an exception was occurred , caused by :{}", e.getMessage());
}
}
}
如果消息的路由键是下面的like.orange.color,路由键会匹配到第一个队列中。运行发送端,效果如图。
查看http://localhost:15672管理页面,队列一收到一条消息。
同理,如果发送消息的路由键是 lazy开头的,那么队列二应该能收到消息。运行发送端后,查看管理页面,发现队列二确实也收到了消息。
最后运行消费端Consumer01与Consumer02,可以就能够消费掉队列中存放的数据了。消费完数据后,队列中的数据数量变成0。
三、总结
Topic类型的交换机
任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上。使用的是一种正则匹配规则。生产者会发送一个带路由键的消息。Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。
Fanout类型的交换机
发送到exchange的所有消息会被转发到与exchange绑定的所有queue,不需要处理路由
Direct类型的交换机
需要处理路由键。该交换机收到消息后会把消息发送到接收指定routing-key的queue中。
四、源代码下载
源代码地址:https://github.com/hairdryre/Study_RabbitMQ
阅读更多:从头开始学RabbimtMQ目录贴