目录
一、文章主旨
第二小节介绍了 RabbitMQ
及其控制台的安装,以及常用操作;
第三小节介绍了常见的五种工作模式,并对对其进阶比较做了描述,同时配以代码辅助理解;
第四小节则是对springboot
框架引入RabbitMQ
作了介绍,并以通配符模式作为示例;
二、RabbitMQ Management控制台
2.1 创建用户
这里的Tags代表着用户权限,有以下几种:
1、 超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、 监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、 策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
4、 普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、 其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
2.2 创建Virtual Host
这里的Virtual Host
就类似于数据库中的database
概念,每一个都相当于一个独立的Rabbit MQ
服务器,相互隔离不能互相通信,并且vhost
的命名一般以/
开头;
2.3 给予用户vhost权限
三、工作模式
3.0 环境及工具类:
-
Builder构建器源码博客:用来创建连接工具类;
-
父依赖:
生产者及消费者采用子模块的方式,依赖也只有amqp-client
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.coderwhat</groupId>
<artifactId>rabbitmqDemo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>producer</module>
<module>consumer</module>
</modules>
<properties>
<java_version>1.8</java_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java_version}</source>
<target>${java_version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 连接工厂:
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//RabbitMQ连接工厂
public class RQConnectionFactory {
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = Builder.of(ConnectionFactory::new)
//主机地址
.with(ConnectionFactory::setHost,"localhost")
//连接端口,默认为5672
.with(ConnectionFactory::setPort,5672)
//vhost名称
.with(ConnectionFactory::setVirtualHost,"/rqhost")
//连接用户名密码
.with(ConnectionFactory::setUsername,"admin")
.with(ConnectionFactory::setPassword,"admin")
.build();
return factory.newConnection();
}
}
3.1 简单模式
简单模式,就是简单的生产与消费的消息队列:
P:生产者,发送消息的程序;
C:消费者:消息的接受者,会一直等待消息到来(监听状态)。
queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
下列为生产者、消费者的步骤及示例代码:
步骤 | 生产者 | 消费者 |
---|---|---|
1 | 获取连接 | 获取连接 |
2 | 创建并声明频道与消息队列 | 创建并声明频道与消息队列 |
3 | 定义并发送消息 | 创建消费者,设置消息处理方式(后续监听调用) |
4 | 关闭连接、通道资源 | 监听队列 |
- 生产者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 简单模式 生产者
* @Date: 2021/4/7 19:16
* @Author: Evan
* @Version: v1.0.0
*/
public class MySimpleProducer {
//定义队列名
private static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
/**
* 3.声明队列:
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//4.定义要发送的信息
String message = "简单模式实例";
/**
* 5.发送消息
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("已成功发送简单消息:" + message);
//6.关闭频道、连接资源
channel.close();
connection.close();
}
}
- 消费者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 简单模式 消费者
* @Date: 2021/4/7 20:27
* @Author: Evan
* @Version: v1.0.0
*/
public class MySimpleConsumer {
private static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明频道,与生产者保持一致
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//4.创建消费者,设置消息处理方式(待后续监听调用)
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
* 消费消息方法需要重写
* @param consumerTag 消费者标签,channel.basicConsume时可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
* (收到消息失败后是否需要重新发送)
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey());
System.out.println("交换机为:" + envelope.getExchange());
System.out.println("消息id为:" + envelope.getDeliveryTag());
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 5.监听队列
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消
息,设置为false则需要手动确认
* 参数3:消息接收到后回调处理方法(消费)
*/
channel.basicConsume(QUEUE_NAME,true,consumer);
//这里无需再关闭资源,持续监听
}
}
3.2 工作队列模式
相较于普通模式,工作队列模式中可以有一个或多个消费端,共同消费同一队列中的消息,此时多个消费者之间对于同一个消息是竞争关系;对于任务过重的情况可以提高任务处理速度;
代码基本与简单模式一致,只需多复制一份消费者代码,此时我们可以在消费者服务中设置频道每次最大处理消息数量,并在回调的时手动声明当前这条消息已被处理(简单模式代码中设置的时自动确认);
- 消费者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 工作队列模式 消费者
* @Date: 2021/4/8 22:37
* @Author: Evan
* @Version: v1.0.0
*/
public class MyWorkConsumer {
private static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明频道,与生产者保持一致
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//4.设置一次只能接收并处理一个消息
channel.basicQos(1);
//5.创建消费者,设置消息处理方式(待后续监听调用)
DefaultConsumer consumer = new DefaultConsumer(channel){
/**
* 消费消息方法需要重写
* @param consumerTag 消费者标签,channel.basicConsume时可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
* (收到消息失败后是否需要重新发送)
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey());
System.out.println("交换机为:" + envelope.getExchange());
System.out.println("消息id为:" + envelope.getDeliveryTag());
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
//确认消息
/**
* 参数1:消息id
* 参数2:是否确认,false表示只有当前这条被确认
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
/**
* 6.监听队列
* 前面手动确认消息已被处理,这里就只用设置为false了
*/
channel.basicConsume(QUEUE_NAME,false,consumer);
//这里无需再关闭资源,持续监听
}
}
3.3 发布与订阅模式
订阅模式相较于工作队列模式,多了一个 X (交换机),生产者 P 发送的消息不再直接送到队列中而是交换机,根据交换机类型的不同决定如何将消息传递给后续队列中(交换机不存储消息,如果无法传递出去就会丢失),常见有以下三种交换机:
- Fanout:广播,将消息交给所有绑定的交换机;
- Direct:定向,将消息交给符合指定
routing key
的队列;- Topic:通配符,叫消息符合
routing parttern
的队列;
发布与订阅模式:
发布与订阅模式就是使用的 Fanout 交换机,消费者监听自己的队列,生产者将消息发送给交换机后,交换机转发至每个绑定的队列中,因此都能接收到消息;
下列为生产者、消费者的步骤及示例代码:
此时我们在代码中可以看到,工作队列模式只是没有设置与手动绑定交换机(底层使用默认交换机);
步骤 | 生产者 | 消费者 |
---|---|---|
1 | 获取连接 | 获取连接 |
2 | 创建频道 | 创建频道 |
3 | 声明频道绑定的多个消息队列 | 声明当前消费者绑定的消息队列 |
4 | 声明交换机名称与类型(Fanout),并绑定绑定队列 | 声明交换机名称与类型(Fanout),并绑定绑定队列 |
3 | 定义并发送消息 | 创建消费者,设置消息处理方式(后续监听调用) |
4 | 关闭连接、通道资源 | 监听队列 |
- 生产者:
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 发布订阅模式生产者
* @Date: 2021/4/11 16:07
* @Author: Evan
* @Version: v1.0.0
*/
public class MyFanoutProducer {
//队列名称
private static final String FANOUT_QUEUE_1 = "fanout_queue_1";
private static final String FANOUT_QUEUE_2 = "fanout_queue_2";
//交换机名称
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明两个队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(FANOUT_QUEUE_1,true,false,false,null);
channel.queueDeclare(FANOUT_QUEUE_2,true,false,false,null);
//4.声明交换机并将队列绑定到交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
channel.queueBind(FANOUT_QUEUE_1,EXCHANGE_NAME,"");
channel.queueBind(FANOUT_QUEUE_2,EXCHANGE_NAME,"");
//5.发布消息
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
String message = "发布订阅模式";
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
System.out.println("已成功发送(发布订阅模式消息):" + message);
//6.关闭资源
channel.close();
connection.close();
}
}
- 消费者
每个消费者只用更改其所绑定的队列即可
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 发布订阅模式消费者
* @Date: 2021/4/11 16:08
* @Author: Evan
* @Version: v1.0.0
*/
public class MyFanoutConsumer {
//队列名称
private static final String FANOUT_QUEUE_1 = "fanout_queue_1";
//交换机名称
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明当前消费者所绑定的队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(FANOUT_QUEUE_1,true,false,false,null);
//4.声明将当前队列绑定到交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
channel.queueBind(FANOUT_QUEUE_1,EXCHANGE_NAME,"");
//5.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* 消费消息方法需要重写
* @param consumerTag 消费者标签,channel.basicConsume时可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
* (收到消息失败后是否需要重新发送)
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey());
System.out.println("交换机为:" + envelope.getExchange());
System.out.println("消息id为:" + envelope.getDeliveryTag());
System.out.println("消费者1-接收到的消息为:" + new String(body, "UTF-8"));
}
};
//6.监听并回调消费者
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,
* mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(FANOUT_QUEUE_1, true, consumer);
}
}
3.4 路由模式
路由模式就是在发布订阅模式的基础上,新增了对路由key的限定,在发送消息时指定 routing key
,这样交换机只会将消息发送到routing key
匹配的队列中;下图所示就代表着第二个队列可以匹配routing key
为 info、error或warning
;
下列为生产者、消费者的步骤及示例代码:
routing key
一般由一个或多个以.
间隔的单词组成;
步骤 | 生产者 | 消费者 |
---|---|---|
1 | 获取连接 | 获取连接 |
2 | 创建频道 | 创建频道 |
3 | 声明频道绑定的多个消息队列 | 声明当前消费者绑定的消息队列 |
4 | 声明交换机名称与类型(Direct),并绑定队列(指定队列routing key ) | 声明交换机名称与类型(Direct),并绑定队列(指定队列routing key ) |
3 | 定义并发送消息(指定routing key ) | 创建消费者,设置消息处理方式(后续监听调用) |
4 | 关闭连接、通道资源 | 监听队列 |
- 生产者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 路由模式生产者
* @Date: 2021/4/11 17:02
* @Author: Evan
* @Version: v1.0.0
*/
public class MyDirectProducer {
//交换机名称
private static final String EXCHANGE_NAME = "direct_exchange";
//队列名称
private static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
//队列名称
private static final String DIRECT_QUEUE_UPDATE = "direct_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明两个队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE,true,false,false,null);
//4.声明交换机 -> 将队列绑定到交换机并指定队列 routing key
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(DIRECT_QUEUE_INSERT,EXCHANGE_NAME,"insert");
channel.queueBind(DIRECT_QUEUE_UPDATE,EXCHANGE_NAME,"update");
//5.发布消息并指定消息的 routing key
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
String message = "insert路由模式";
channel.basicPublish(EXCHANGE_NAME,"insert",null,message.getBytes());
String message1 = "update路由模式";
channel.basicPublish(EXCHANGE_NAME,"update",null,message1.getBytes());
System.out.println("已成功发送(insert路由模式消息):" + message);
System.out.println("已成功发送(update路由模式消息):" + message1);
//6.关闭资源
channel.close();
connection.close();
}
}
- 消费者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 路由模式消费者
* @Date: 2021/4/11 17:01
* @Author: Evan
* @Version: v1.0.0
*/
public class MyDirectConsumer {
//交换机名称
private static final String EXCHANGE_NAME = "direct_exchange";
//队列名称
private static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明当前消费者所绑定的队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
//4.声明将当前队列绑定到交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(DIRECT_QUEUE_INSERT,EXCHANGE_NAME,"insert");
//5.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* 消费消息方法需要重写
* @param consumerTag 消费者标签,channel.basicConsume时可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
* (收到消息失败后是否需要重新发送)
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey());
System.out.println("交换机为:" + envelope.getExchange());
System.out.println("消息id为:" + envelope.getDeliveryTag());
System.out.println("消费者1-接收到的消息为:" + new String(body, "UTF-8"));
}
};
//6.监听并回调消费者
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,
* mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(DIRECT_QUEUE_INSERT, true, consumer);
}
}
3.5 通配符模式
通配符模式与路由模式一样,都是通过routing key
指定匹配条件,不同的是通配符模式可以使用通配符,并且通配符有以下两种:
* :匹配一个单词,如a.*
可以匹配a.b,a.c
# :匹配多个词,如 a.#
可以匹配a.b,a.b.c
通配符模式能实现发布订阅模式、路由模式的功能,并且还能使用通配符,显得更灵活一些;
下列为生产者、消费者的步骤及示例代码:
当我们不再在生产者里声明队列并绑定交换机时,需要先将消费者运行起来才不会出现消息丢失的情况,因为生产者单纯将消息传递给交换机,其不会存储消息;
步骤 | 生产者 | 消费者 |
---|---|---|
1 | 获取连接 | 获取连接 |
2 | 创建频道 | 创建频道 |
3 | 声明交换机名称与类型(Topic) | 声明当前消费者绑定的消息队列 |
4 | 定义并发送消息(指定routing key ) | 声明交换机名称与类型(Topic),并绑定队列(指定队列routing key 通配符) |
3 | 关闭连接、通道资源 | 创建消费者,设置消息处理方式(后续监听调用) |
4 | 监听队列 |
- 生产者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 通配符模式生产者
* @Date: 2021/4/11 17:52
* @Author: Evan
* @Version: v1.0.0
*/
public class MyTopicProducer {
//交换机名称
private static final String EXCHANGE_NAME = "topic_exchange";
//队列名称
private static final String TOPIC_QUEUE_INSERT = "topic_queue_insert";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明交换机
// 当我们不再在生产者里声明队列时,需要先将消费者运行起来才不会出现消息丢失的情况,
// 因为生产者单纯将消息传递给交换机,其不会存储消息
/*
*//**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*//*
channel.queueDeclare(DIRECT_QUEUE_INSERT,true,false,false,null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE,true,false,false,null);
//3声明交换机 -> 将队列绑定到交换机并指定队列 routing key
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
channel.queueBind(TOPIC_QUEUE_INSERT,EXCHANGE_NAME,"item.insert");
channel.queueBind(TOPIC_QUEUE_INSERT,EXCHANGE_NAME,"item.update");*/
channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.TOPIC);
//4.发布消息并指定消息的 routing key,后续将通过通配符接受 routing key
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
String message = "insert通配符模式";
channel.basicPublish(EXCHANGE_NAME,"item.insert",null,message.getBytes());
String message1 = "update通配符模式";
channel.basicPublish(EXCHANGE_NAME,"item.update",null,message1.getBytes());
System.out.println("已成功发送(insert通配符模式消息):" + message);
System.out.println("已成功发送(update通配符模式消息):" + message1);
//5.关闭资源
channel.close();
connection.close();
}
}
- 消费者
import com.coderwhat.util.RQConnectionFactory;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Description: 通配符模式消费者
* @Date: 2021/4/11 17:51
* @Author: Evan
* @Version: v1.0.0
*/
public class MyTopicConsumer {
//交换机名称
private static final String EXCHANGE_NAME = "topic_exchange";
//队列名称
private static final String TOPIC_QUEUE_INSERT = "topic_queue_insert";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = RQConnectionFactory.getConnection();
//2.创建频道
Channel channel = connection.createChannel();
//3.声明当前消费者所绑定的队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(TOPIC_QUEUE_INSERT,true,false,false,null);
//4.声明将当前队列绑定到交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//绑定队列与交换机,并声明 routing key 通配符,替换原来一个个声明路由的方式
channel.queueBind(TOPIC_QUEUE_INSERT,EXCHANGE_NAME,"item.*");
//channel.queueBind(TOPIC_QUEUE_INSERT,EXCHANGE_NAME,"item.insert");
//channel.queueBind(TOPIC_QUEUE_INSERT,EXCHANGE_NAME,"item.update");
//5.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
/**
* 消费消息方法需要重写
* @param consumerTag 消费者标签,channel.basicConsume时可以指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
* (收到消息失败后是否需要重新发送)
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key为:" + envelope.getRoutingKey());
System.out.println("交换机为:" + envelope.getExchange());
System.out.println("消息id为:" + envelope.getDeliveryTag());
System.out.println("消费者1-接收到的消息为:" + new String(body, "UTF-8"));
}
};
//6.监听并回调消费者
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,
* mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(TOPIC_QUEUE_INSERT, true, consumer);
}
}
三、springboot集成
以通配器模式为示例,同样使用父模块管理依赖版本
todo:这里有一个小问题,就是junit5
版本使用碰到一些问题,各种小异常,使用junit4
则没问题
下列为生产者和消费者步骤列表:
步骤 | 生产者 | 消费者 |
---|---|---|
1 | 引入依赖,编写启动类 | 引入依赖编写启动类 |
2 | 编写配置类(声明队列、交换机、二者绑定) | 使用注解编写监听器 |
3 | 编写测试类,发送消息并指定routing key |
父依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.6.RELEASE</version>
</parent>
<groupId>org.coderwhat</groupId>
<artifactId>rabbitmq-demo-2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>producer</module>
<module>consumer</module>
</modules>
<properties>
<java_version>1.8</java_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!-- 插件版本保持与父类一致 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java_version}</source>
<target>${java_version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.6.RELEASE</version>
<executions>
<execution>
<goals>
<goal>
<!--打包时引入第三方依赖生成 big jar -->
repackage
</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3.1 生产者
依赖套用即可,启动类较简单,省略
- 生产者配置类
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description: 生产者配置类,用来声明队列、交换机,绑定队列和交换机
* @Date: 2021/4/12 21:51
* @Author: Evan
* @Version: v1.0.0
*/
@Configuration
public class TopicRabbitMQConfig {
//交换机名
public static final String EXCHANGE_NAME = "sb_topic_exchange";
//队列名
public static final String TOPIC_QUEUE = "sb_topic_queue";
//声明队列
@Bean
public Queue topicQueue(){
return QueueBuilder.durable(TOPIC_QUEUE).build();
}
//声明topic交换机
@Bean
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//绑定队列与交换机,并声明通配符,无参数
@Bean
public Binding bindQueueExchange(
@Qualifier("topicQueue") Queue queue,
@Qualifier("topicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.*").noargs();
}
}
- 测试类
import com.coderwhat.rabbit.config.TopicRabbitMQConfig;
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;
/**
* @Description: topic生产者测试类
* @Date: 2021/4/12 23:17
* @Author: Evan
* @Version: v1.0.0
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerApplicationTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
rabbitTemplate.convertAndSend(
TopicRabbitMQConfig.EXCHANGE_NAME,
"item.insert","这个消息是insert");
rabbitTemplate.convertAndSend(
TopicRabbitMQConfig.EXCHANGE_NAME,
"item.update","这个消息是update");
rabbitTemplate.convertAndSend(
TopicRabbitMQConfig.EXCHANGE_NAME,
"item.delete","这个消息是delete");
}
}
3.2 消费者
- 监听类:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @Description: 监听topic queue队列
* @Date: 2021/4/12 23:09
* @Author: Evan
* @Version: v1.0.0
*/
@Component
public class MyListener {
/**
* 监听队列消息,队列名保持一致
* @param message 接收到的消息
*/
@RabbitListener(queues = "sb_topic_queue")
public void topicListener(String message){
System.out.println("接收到的消息为:" + message);
}
}