1. 消息队列概述
消息队列(Message Queue,简称MQ)是应用程序和应用程序之间的通信;
作用:
可以将无需即时返回的耗时的操作进行异步处理从而提高系统的吞吐量;可以实现程序之间的解耦合。
实现方式:AMQP、JMS
常见产品:activeMQ、zeroMQ、RabbitMQ、RocketMQ、kafka
其中咱们要学习的RabbitMQ是基于AMQP协议的。
2.RabbitMQ的工作模式
目前官网(https://www.rabbitmq.com/getstarted.html)上有这么几种工作模式:
我们主要学习前5种工作模式,为了方便后面的操作,我们先创建my-rabbitmq的工程;用于测试RabbitMQ的消息收发;为了操作方便,采用maven管理工程
我这是搭建好的一个maven工程
搭建好工程之后,需要在pom文件中引入RabbitMQ的依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
到这儿准备工作就完成了
2.1 简单模式
简单模式:
一个生产者,一个消费者;
生产者发送消息到队列中,消费者从队列中接收消息
2.1.1 生产者
这一部分的目标是实现生产者发送消息到队列的代码
生产者实现发送消息的步骤分为6步:
- 创建连接工厂(设置RabbitMQ的连接参数);
- 创建连接
- 创建频道
- 声明队列
- 发送消息
- 关闭资源
代码实现如下
package com.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 简单模式:发送消息
*/
public class Producer {
//设置队列名
static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂(设置RabbitMQ的连接参数);
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数:
// 主机:默认localhost
connectionFactory.setHost("localhost");
//连接端口:默认5672
connectionFactory.setPort(5672);
//虚拟主机:默认/
connectionFactory.setVirtualHost("testhost");
//用户名:默认guest
connectionFactory.setUsername("admin");
//密码:默认guest
connectionFactory.setPassword("admin");
// 2. 创建连接
Connection connection = connectionFactory.newConnection();
// 3. 创建频道
Channel channel = connection.createChannel();
// 4. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 5. 发送消息
String msg = "你好,小仙女";
/**
* 参数1:交换机名称,如果没有指定空字符串(表示使用默认交换机)
* 参数2:路由key,简单模式中可以使用队列名
* 参数3:消息其他属性
* 参数4:消息内容
*/
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("已发送:" + msg);
// 6. 关闭资源
channel.close();
connection.close()
}
}
【注】在设置连接工厂的时候;如果没有指定连接的参数则会有默认值
2.1.2 消费者
这一部分编写消费者代码,从队列(与生产者发送消息时的队列一致:simple_queue)中接收消息
实现消费者的步骤如下:
- 创建连接工厂(设置RabbitMQ的连接参数);
- 创建连接
- 创建频道
- 声明队列
- 发送消息
- 关闭资源
实现代码
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂;
// 2. 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 3. 创建频道
Channel channel = connection.createChannel();
// 4. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
// 5. 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("接收到的消息:" + new String(body, "utf-8"));
}
};
// 6. 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.QUEUE_NAME, true, defaultConsumer);
}
}
2.1.3 抽取工具类
到这可以看出不管是生产者,还是消费者,都要创建连接,且创建方式相同,可抽取出来
public class ConnectionUtil {
public static Connection getConnection() throws IOException, TimeoutException {
// 1. 创建连接工厂(设置RabbitMQ的连接参数);
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置参数:
// 主机:默认localhost
connectionFactory.setHost("localhost");
//连接端口:默认5672
connectionFactory.setPort(5672);
//虚拟主机:默认/
connectionFactory.setVirtualHost("testhost");
//用户名:默认guest
connectionFactory.setUsername("admin");
//密码:默认guest
connectionFactory.setPassword("admin");
// 2. 创建连接
return connectionFactory.newConnection();
}
}
2.2 Work queues工作队列模式
工作队列模式:在同一个队列中可以有多个消费者,消费者之间对于消息的接收是竞争关系。
一个消息只能被一个消费者接收,其他消费者是不能接收到同一条消息的。
应用场景:可以在消费者端处理任务比较耗时的时候,添加对同一个队列的消费者来提高任务处理能力。
2.2.1 生产者
模拟生产者发送30个消息
代码如下
public class Producer {
//设置队列名
static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
// 3. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for(int i = 1; i <= 30; i++){
// 4. 发送消息
String msg = "你好,小仙女------工作队列模式" + i;
/**
* 参数1:交换机名称,如果没有指定空字符串(表示使用默认交换机)
* 参数2:路由key,简单模式中可以使用队列名
* 参数3:消息其他属性
* 参数4:消息内容
*/
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
System.out.println("已发送:" + msg);
}
// 5. 关闭资源
channel.close();
connection.close();
}
}
2.2.2 消费者
创建两个消费者监听同一队列,查看两个消费者的接收消息是否存在重复。
两个消费者代码是相同
代码如下
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂;
// 2. 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 3. 创建频道
Channel channel = connection.createChannel();
// 4. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//每次可以预取多少个消息
channel.basicQos(1);
// 5. 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("消费者1-----接收到的消息:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
/**
* 参数1:消息id
* 参数2:false表示只有当前这条消息被处理
*/
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 6. 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.QUEUE_NAME, true, defaultConsumer);
}
}
测试结果两个消费者接收到的消息是不会重复的,也就是一个消息只能被一个消费者接收,大家可自行去验证。
2.3 Publish/Subscribe发布与订阅模式
订阅模式与前面的两种模式比较:多了一个角色Exchange交换机,接收生产者发送的消息并决定如何投递消息到其绑定的队列;消息的投递决定于交换机的类型。
交换机的类型:广播(fanout)、定向(direct)、通配符(topic)
交换机只做消息转发,自身不存储数据
发布与订阅模式特点:一个消息可以被多个消息接收;其实是使用了订阅模式,交换机类型为:fanout广播。
2.3.1 生产者
创建生产者发送10个消息
public class Producer {
//交换机名称
static final String FANOUT_EXCHANGE = "fanout_exchange";
//设置队列名
static final String FANOUT_QUEUE_1 = "fanout_queue_1";
static final String FANOUT_QUEUE_2 = "fanout_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
//声明交换机(参数1:交换机名称 参数2:交换机类型(fanout direct topic))
channel.exchangeDeclare(FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
// 3. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);
//队列绑定到交换机
//参数 队列名, 交换机,路由key
channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHANGE, "");
channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHANGE, "");
// 4. 发送消息
for(int i = 1; i <= 10; i++){
String msg = "你好,小仙女---发布与订阅模式---" + i;
/**
* 参数1:交换机名称,如果没有指定空字符串(表示使用默认交换机)
* 参数2:路由key,简单模式中可以使用队列名
* 参数3:消息其他属性
* 参数4:消息内容
*/
channel.basicPublish(FANOUT_EXCHANGE, "", null, msg.getBytes());
System.out.println("已发送:" + msg);
}
// 5. 关闭资源
channel.close();
connection.close();
}
}
2.3.2 消费者
创建2个消费者来接收消息,两个消费者代码基本一致,队列名与生产者声明的两个队列分别对应就可以了。这只列举一个消费者的代码,另一个消费者可复制,然后修改其队列名
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
// 4. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);
//5、队列绑定到交换机上
channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHANGE, "");
// 5. 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("消费者1-----接收到的消息:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
/**
* 参数1:消息id
* 参数2:false表示只有当前这条消息被处理
*/
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 6. 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.FANOUT_QUEUE_1, true, defaultConsumer);
}
}
两个消费者都能接收到生产者发送的消息,且接收到的消息是一样的。
2.4 Routing路由模式
Routing路由模式要求队列绑定到交换机的时候指定路由key;消费者发送时需要携带路由key;只有消息的路由key与队列路由key完全一致才能让队列接收到消息。
交换机类型为:direct定向。
2.4.1 生产者
创建生产者,发送两条消息(路由key分别为insert、update)
public class Producer {
//交换机名称
static final String DIRECT_EXCHANGE = "direct_exchange";
//设置队列名
static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
static final String DIRECT_QUEUE_UPDATE = "direct_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接
Connection connection = ConnectionUtil.getConnection();
// 2. 创建频道
Channel channel = connection.createChannel();
//声明交换机(参数1:交换机名称 参数2:交换机类型(fanout direct topic))
channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
// 3. 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);
//队列绑定到交换机
//参数 队列名, 交换机,路由key
channel.queueBind(DIRECT_QUEUE_INSERT, DIRECT_EXCHANGE, "insert");
channel.queueBind(DIRECT_QUEUE_UPDATE, DIRECT_EXCHANGE, "update");
// 4. 发送消息
String msg = "你好,小仙女---routing key为insert---";
/**
* 参数1:交换机名称,如果没有指定空字符串(表示使用默认交换机)
* 参数2:路由key,简单模式中可以使用队列名
* 参数3:消息其他属性
* 参数4:消息内容
*/
channel.basicPublish(DIRECT_EXCHANGE, "insert", null, msg.getBytes());
System.out.println("已发送:" + msg);
msg = "你好,小仙女---routing key为update---";
channel.basicPublish(DIRECT_EXCHANGE, "update", null, msg.getBytes());
System.out.println("已发送:" + msg);
// 6. 关闭资源
channel.close();
connection.close();
}
}
2.4.2 消费者
创建两个消费者,监听的队列分别绑定路由key为:insert、update
- 消息中路由key为insert的会被绑定路由key为insert的队列接收 并被其监听的消费者接收、处理;
- 消息中路由key为update的会被绑定路由key为update的队列接收 并被其监听的消费者接收、处理;
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_INSERT, true, false, false, null);
//队列绑定到交换机上
//参数:1.队列名 2.交换机 3.路由key
channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHANGE, "insert");
// 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("消费者1-----接收到的消息:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
/**
* 参数1:消息id
* 参数2:false表示只有当前这条消息被处理
*/
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 6. 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, defaultConsumer);
}
}
消费者2与消费者1代码大致相同
只需把其中相关的队列名称以及路由key改为update相关的即可
测试结果就是消费者1只能接收路由key为insert的消息
消费者2只能接收路由key为update的消息
2.5 Topics通配符模式
Topics类型与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列,只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
2.5.1 生产者
创建生产者,发送包含有item.insert,item.update,item.delete的3种路由key消息
public class Producer {
//交换机名称
static final String TOPIC_EXCHANGE = "topic_exchange";
//设置队列名
static final String TOPIC_QUEUE_1 = "topic_queue_1";
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
Connection connection = ConnectionUtil.getConnection();
// 创建频道
Channel channel = connection.createChannel();
//声明交换机(参数1:交换机名称 参数2:交换机类型(fanout direct topic))
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
// 发送消息
String msg = "新增,小仙女,通配符模式---routing key为item.insert---";
/**
* 参数1:交换机名称,如果没有指定空字符串(表示使用默认交换机)
* 参数2:路由key,简单模式中可以使用队列名
* 参数3:消息其他属性
* 参数4:消息内容
*/
channel.basicPublish(TOPIC_EXCHANGE, "item.insert", null, msg.getBytes());
System.out.println("已发送:" + msg);
msg = "修改,小仙女,通配符模式---routing key为item.update---";
channel.basicPublish(TOPIC_EXCHANGE, "item.update", null, msg.getBytes());
System.out.println("已发送:" + msg);
msg = "删除,小仙女,通配符模式---routing key为item.delete---";
channel.basicPublish(TOPIC_EXCHANGE, "item.delete", null, msg.getBytes());
System.out.println("已发送:" + msg);
// 6. 关闭资源
channel.close();
connection.close();
}
}
2.5.2 消费者
创建两个消费者来接收消息
消费者1:监听的队列绑定到交换机的路由key为:item.update,item.delete
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
// 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_1, true, false, false, null);
//队列绑定到交换机上
//参数:1.队列名 2.交换机 3.路由key
channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHANGE, "item.update");
channel.queueBind(Producer.TOPIC_QUEUE_1, Producer.TOPIC_EXCHANGE, "item.delete");
// 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("消费者1-----接收到的消息:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
/**
* 参数1:消息id
* 参数2:false表示只有当前这条消息被处理
*/
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.TOPIC_QUEUE_1, true, defaultConsumer);
}
}
消费者2:监听的队列绑定到交换机的路由key为:item.*
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接;(抽取一个获取连接的工具类)
Connection connection = ConnectionUtil.getConnection();
// 创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
// 声明队列
/**
* 参数1: 队列名称、
* 参数2:是否定义持久化队列(消息会持久化保存在服务器上)
* 参数3:是否独占本次连接、
* 参数4: 是否在不使用时自动删除队列、
* 参数5:其他参数
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_2, true, false, false, null);
//队列绑定到交换机上
//参数:1.队列名 2.交换机 3.路由key
channel.queueBind(Producer.TOPIC_QUEUE_2, Producer.TOPIC_EXCHANGE, "item.*");
// 创建消费者(接收消息并处理消息)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机:" + envelope.getExchange());
//消息id
System.out.println("消息id:" + envelope.getDeliveryTag());
//接收到的消息
System.out.println("消费者2-----接收到的消息:" + new String(body, "utf-8"));
Thread.sleep(1000);
//确认消息
/**
* 参数1:消息id
* 参数2:false表示只有当前这条消息被处理
*/ channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 监听队列
/**
* 参数1:队列名
* 参数2:是否自动确认,设置为true表示消息接收到自动向MQ回复接收到了,MQ会将消息从队列中删除;
* 如果设置为false则需要手动确认
* 参数3:消费者
*/
channel.basicConsume(Producer.TOPIC_QUEUE_2, true, defaultConsumer);
}
}
测试结果是,消费者1接收到的是路由key 为item.update,item.delete的消息
消费者2 接收到的是所有的消息
3. Springboot整合RabbitMQ
创建springboot-rabbitmq-producer工程用于生产消息;创建springboot-rabbitmq-consumer工程用于接收消息。在这个demo中用到的是通配符模式
3.1 生产者工程
首先先创建一个springboot-rabbitmq-producer生产者工程
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
添加配置文件application.yml
配置RabbitMQ的连接参数:主机、连接端口、虚拟主机、用户名、密码;(这需要改成自己的)
spring:
rabbitmq:
host: localhost
port: 5672
virtual-host: testhost
username: admin
password: admin
声明交换机、队列并将队列绑定到交换机,指定路由key(item.#),可创建一个配置类
@Configuration
public class RabbitMQConfig {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";
//队列名称
public static final String ITEM_QUEUE = "item_queue";
//声明交换机
@Bean("itemTopicExchange")
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
//声明队列
@Bean("itemQueue")
public Queue itemQueue(){
return QueueBuilder.durable(ITEM_QUEUE).build();
}
//将队列绑定到交换机
@Bean
public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,
@Qualifier("itemTopicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
}
3.2 消费者工程
创建springboot-rabbitmq-consumer消费者工程,接收消息
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件与生产者一致
编写监听器类
@Component
public class Mylistener {
/**
* 接收队列消息
* 若要接收多个队列消息可以@RabbitListener(queues = {"item_queue",""item_queue1""})
* @param msg
*/
@RabbitListener(queues = "item_queue")
public void myListener(String msg){
System.out.println("消费者接收到的消息:" + msg);
}
}
接收消息的队列名称要与生产者发送消息时的队列名称一致
3.3 测试消息发送和接收
生产者:编写测试类RabbitMQTest,利用RabbitTemplate发送3条消息,这3条消息的路由key分别是item.insert,item.update,item.delete
编写测试类如下
package com.rabbitmq;
import com.rabbitmq.config.RabbitMQConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.insert", "新增,路由key为item.insert");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.update", "修改,路由key为item.update");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"item.delete", "删除,路由key为item.delete");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
"a.item.delete", "删除,路由key为item.delete");
}
}
消费者:在IDEA控制台查看是否能接收到符合路由key的消息。
可以看到接收到的消息是复合路由key为item.#的消息。