一. RabbitMQ消息处理
在上一节我们讲到了RabbitMQ的简单架构,我们先回顾一下RabbitMQ中消息的流转流程,这个概念会在本节中使用到。
1.生产者生产消息后,会绑定一个路由键将消息投递到交换器中
2.队列通过路由键绑定到交换器
3.交换器通过消息的路由键去查找绑定在它上面的队列,通过不同的规则去匹配队列绑定的路由键,匹配成功后将消息路由到队列上,遍历所有队列后都没有匹配的消息将无法路由到队列
4.消费者监听队列,一旦有消息路由到队列,消费者就会从队列中取出消息或者是RabbitMQ将消息推送给消费者进行消费
上面的流程是在正常情况下的消息处理流程,对于投递失败的消息和消费失败的消息,我们会在后续的知识点中讲到
二. 交换器类型
不同类型的交换器有不同的路由键键匹配规则,我们先来看看RabbitMQ中有哪几种交换器
1 Direct
只有当路由键完全匹配时,才会将消息投递到相应的队列中
2 Fanout
消息广播到绑定的队列,即该类型的交换器不会根据路由键去匹配队列,只要有消息投递到该交换器,它就会将消息投递到所有绑定在该交换器上的队列
3 Topic
Topic交换器可以使用通配符("*" 和 "#")使来自不同源头的消息到达统一队列。”.”将路由键分为了几个标识符," * "匹配1个,"#"匹配一个或多个
eg:有四个队列,queue1绑定路由键"usa.#",queue2绑定路由键"#.news",queue3绑定路由键"*.weather",queue4绑定路由键"europe.*"
四条消息:message1绑定的路由键为"usa.news",message2绑定的路由键为"usa.weather",message3绑定的路由键为"europe.news",message4绑定的路由键为"errope.weather"
路由后:
queue1能收到消息message1,message2
queue2能收到消息message1,message3
queue3能收到消息message2,message4
queue4能收到消息message3,message4
需要注意一点是,"*"是匹配一个,"#"匹配多个。若是消息绑定路由键为usa.weather.cloud,那么"usa.*"无法匹配该路由键,"usa.#"能匹配,此时也可以使用"usa.*.*"匹配
4 Headers
由于headers交换器基本不用,这里就不做讲解
三. 使用RabbitMQ原生API
使用RabbitMQ原生API来发送和消费消息时,按照如下流程
生产者:
获取连接工厂
ConnectionFactory factory = new ConnectionFactory();
新建连接
public Connection newConnection();
创建信道
Channel createChannel();
声明交换器
Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type)
args1 交换器名称,args2交换器类型
发送消息
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
args1 交换器名,args2 路由键,args3 消息无法路由时的处理,为true时生产者可以通过回调函数获取消息状态,args4 消息属性,args5 消息体
消费者:
获取连接工厂
ConnectionFactory factory = new ConnectionFactory();
新建连接
public Connection newConnection();
创建信道
Channel createChannel();
声明交换器
Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type)
args1 交换器名称,args2交换器类型
声明队列
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map
args1 队列名,arg2 是否持久化,args3 是否独占队列,args4 自动删除,args5 其他属性
队列绑定
Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map
args1 队列名,args2 交换器名,args3 路由键,arg4 其他属性
消息消费
String basicConsume(String queue, boolean autoAck, Consumer callback)
args1 队列名,args2 是否自动签收,args3 消费者
四. 使用不同的交换器完成消息处理
- Direct Exchange
DirectProducer Demo
public class DirectProducer {
public static final String EXCHANGE_NAME = "direct_exchange";
public static final String ROUTING_KEY = "direct_key";
public static void main(String[] args) throws Exception{
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
// 新建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String msg = "hello rabbitmq";
// 发送消息
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY,null, msg.getBytes());
System.out.println("消息投递完成,消息:" + msg + ", 路由键:" + ROUTING_KEY);
channel.close();
connection.close();
}
}
生产者端结果
DirectConsumer Demo
public class DirectConsumer {
public static void main(String[] args) throws Exception{
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setVirtualHost("/");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(DirectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "direct_queue";
// 声明队列
channel.queueDeclare(queueName, false, false, false, null);
// 将队列绑定到交换器
channel.queueBind(queueName, DirectProducer.EXCHANGE_NAME, DirectProducer.ROUTING_KEY);
// 声明消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("收到的消息:" + msg + ", 路由键:" + envelope.getRoutingKey());
}
};
// 消费者消费指定队列的消息
channel.basicConsume(queueName, true, consumer);
}
}
消费端结果:
- Fanout Exchange
FanoutProducer Demo
public class FanoutProducer {
// 交换器
public static final String EXCHANGE_NAME = "fanout_exchange";
// 路由键
public static final String ROUTING_KEY = "fanout_key";
public static void main(String[] args) throws Exception{
// 获取连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setVirtualHost("/");
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
// 获取连接
Connection connection = connectionFactory.newConnection();
// 声明信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 发送消息
String msg = "hello rabbitmq";
channel.basicPublish(FanoutProducer.EXCHANGE_NAME, ROUTING_KEY, null, msg.getBytes());
System.out.println("发送消息:" + msg + ",路由键:" + ROUTING_KEY);
channel.close();
connection.close();
}
}
生产端结果:
FanoutConsumer Demo
public class FanoutConsumer {
public static void main(String[] args) throws Exception{
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setVirtualHost("/");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(FanoutProducer.EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 队列
String[] queueNames = {"queue1", "queue2", "queue3"};
// 路由键
String[] routingKey = {"fanout_key", "fanout_key1", "fanout_key2"};
// 分别使用不同的路由键声明不同的队列
for (int i = 0; i < 3; i++){
channel.queueDeclare(queueNames[i], false, false, false, null);
channel.queueBind(queueNames[i], FanoutProducer.EXCHANGE_NAME, routingKey[i]);
// 消费消息
channel.basicConsume(queueNames[i], true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消息:" + msg + ",路由键:" + envelope.getRoutingKey());
}
});
}
}
}
消费端结果:
- Topic Exchange
TopicProducer Demo
public class TopicProducer {
// 交换器
public static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) throws Exception{
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
// 新建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 发送消息
String msg = "hello rabbitmq";
String[] routingKey = {"topic.key1", "topic.key2", "topic.key2.test"};
for (int i = 0; i < 3; i++){
channel.basicPublish(TopicProducer.EXCHANGE_NAME, routingKey[i], null, msg.getBytes());
System.out.println("发送消息:" + msg + ", 路由键:" + routingKey[i]);
}
channel.close();
connection.close();
}
}
生产端结果:
TopicConsumer Demo
public class TopicConsumer {
public static void main(String[] args) throws Exception{
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setVirtualHost("/");
// 新建连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 声明交换器
channel.exchangeDeclare(TopicProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "topic_queue";
// 声明队列
channel.queueDeclare(queueName, false, false, false, null);
// 队列绑定
channel.queueBind(queueName, TopicProducer.EXCHANGE_NAME, "topic.*");
// 消息消费
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("消息:" + msg + ", 路由键:" + envelope.getRoutingKey());
}
});
}
}
消费端结果: