何夜息随笔录-RabbitMQ
MQ
:(MessageQueue
)消息队列,什么是消息队列,其实就是消息广播的发送模型,就像从微信公众号发送文章一样,发布者发布一篇文章,那么接受者就能就收到这个消息,或者老师发布了通知,那么学生通过手机能够接收,其实就是在生产者和消费者之间加入了一个消息队列,那么原来生产者要发送信息是直接发送到消费者,就会造成可能有的消费者不需要也接收到了,那么通过中间加一层,没有什么是加一层解决不了的。
通过中间加一层消息队列,那么生产者和消费者就解耦了,消息具体怎么发送,发送给谁,就交给消息队列来处理,这就是消息队列的作用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-brgvjOBK-1631801852258)(https://www.heyexi.com/markdown/image-20210815224148599.png)]
RabbitMQ
是用erlang
语言开发的,基于AMQP
,这是高级消息队列协议,和一些通信协议一样,比如POP3
,Http
,AMQP
也是通信协议,有统一的规范,消息队列就是通过这个协议进行通信的。
市场上还有很多消息队列的产品:Kafka、MetaMQ、Redis、ZeroMQ
等等,Redis
也是哦,它里面有一个订阅发布的消息队列模型,所以也可以算作消息队列。当然这些产品都是用不同的特点的,要根据具体的业务来进行选择,选择RabbitMQ
的原因是SpringBoot
默认是已经集成了这个消息队列,而且使用方便,社区活跃,对高并发处理比较好。
工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8yd5eSi-1631801852278)(https://www.heyexi.com/markdown/image-20210815225405317.png)]
首先是可以有多个生成者Producer
,它想要发送消息,那么需要先和消息队列通过通道
建立连接,然后消息队列服务进程Broker
,里面有一个交换机Exchange
,交换机把消息按照一定的规则,发送到队列Queue
进行暂存,然后消费者Consumer
想要获取消息,首先需要监听队列,然后需要获得消息的话就和和Broker
建立连接,这些连接和消费者一样通过Channel
通道建立,然后从队列中获得消息,这就是一个消息发送到接收的流程。
生产者和消费者都是通过TCP协议通过通道来和消息队列服务进程建立连接的。
下载安装
Windows
下安装需要安装Erlang
语言,然后去RabbitMQ
下载对应的版本,我这里直接在服务器上通过docker
部署安装。
docker pull rabbitmq:management
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjKx8T6t-1631801852280)(https://www.heyexi.com/markdown/image-20210815232921217.png)]
docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 a0a2d74e6e6a
-p指定容器内部端口号与宿主机之间的映射,rabbitMq默认要使用15672为其web端界面访问时端口,5672为数据通信端口
然后还要去记得打开服务器安全组的端口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IW9Ewnd-1631801852284)(https://www.heyexi.com/markdown/image-20210815233816035.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJBAzLGc-1631801852286)(https://www.heyexi.com/markdown/image-20210815233938896.png)]
然后通过ip:15672
端口进行访问,因为我们在运行的使用了management
所以是带有面板的,然后进行登录,默认的账号和密码都是guest
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8jSg3Rqi-1631801852287)(https://www.heyexi.com/markdown/image-20210815234048527.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mepr3Qwx-1631801852288)(https://www.heyexi.com/markdown/image-20210815234140950.png)]
在这里我们就可以看到上面说到的连接、通道、交换机、队列等等比较重要的概念。
我们可以添加一个账号
docker exec -it rabbitmq bin/bash #进入容器
rabbitmqctl add_user root 密码 #添加一个用户
rabbitmqctl set_permissions -p / root ".*" ".*" ".*" #赋予所以权限
rabbitmqctl set_user_tags root administrator # 赋予角色
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GL0U3OF8-1631801852289)(https://www.heyexi.com/markdown/image-20210815234916720.png)]
rabbitmqctl delete_user Username #删除用户
第一个Helloworld
首先需要添加依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.12.0</version>
</dependency>
然后我们建一个生成者的包,实现让生成者生产消息。
根据流程:
- 创建一个连接工厂,通过连接工程能创建连接对象,和
mybatis
是类似的,都是一层建一层,使用了工程模式的设计模式。 - 创建连接对象,就是要指定要连接到哪个
MQ
服务器,设置连接账号密码地址和虚拟机之类的,虚拟机就是一个相当于是一个路由,这个连接的消息都放在这个虚拟机中。 - 通过连接对象创建通道
- 通过通道声明队列
- 通过通道发送消息
/**
* 创建一个生产者测试类
*/
public class ProducerTest
{
public static void main(String[] args)
{
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接对象的属性
connectionFactory.setHost("地址");
connectionFactory.setUsername("root");//用户名
connectionFactory.setPassword("密码");//密码
connectionFactory.setPort(5672);//端口
connectionFactory.setVirtualHost("/");//虚拟机
//通过工厂创建连接对象
Connection connection = null;
try
{
connection = connectionFactory.newConnection();
//创建通道
Channel channel = connection.createChannel();
//声明对象
/**
* 参数:
* 1 队列名称
* 2 是否持久化:就是重启MQ后是否存在
* 3 是否独占连接,就是连接对象销毁后此队列是否还存在
* 4 是否自动删除,就是空闲不用会自己删除
* 5 通过map设置一些扩展参数
*/
channel.queueDeclare("heyexi",true,false,false,null);
//从生成者发送消息
String msg = "你好,这是来自生产者何夜息的消息哦!";
/**
* 参数:
* 1 交换机,不知道就使用默认的交换机
* 2 路由key,就是要通过交换机发送到哪个指定的队列,如果使用默认的就要指定队列名称
* 3 消息的属性
* 4 消息的内容
*/
channel.basicPublish("","heyexi",null,msg.getBytes());
System.out.println("发送成功");
} catch (IOException | TimeoutException e)
{
e.printStackTrace();
} finally
{
}
}
}
然后我们打开后台查看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5O6n0j9-1631801852295)(https://www.heyexi.com/markdown/image-20210816215148693.png)]
然后我们就创建一个消费者,获取上面创建的消息
消费者和上面一样需要获取连接对象,创建队列,不同的只在于生产者是发布消息,而消费者是接受消息,然后定义如何消费消息,就是消费方法。
/**
* 消费者
*/
public class ConsumerTest
{
public static void main(String[] args)
{
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接对象的属性
connectionFactory.setHost("地址");
connectionFactory.setUsername("root");//用户名
connectionFactory.setPassword("密码");//密码
connectionFactory.setPort(5672);//端口
connectionFactory.setVirtualHost("/");//虚拟机
//通过工厂创建连接对象
Connection connection = null;
try
{
connection = connectionFactory.newConnection();
//创建通道
Channel channel = connection.createChannel();
/**
* 参数:
* 1 队列名称
* 2 是否持久化:就是重启MQ后是否存在
* 3 是否独占连接,就是连接对象销毁后此队列是否还存在
* 4 是否自动删除,就是空闲不用会自己删除
* 5 通过map设置一些扩展参数
*/
channel.queueDeclare("heyexi",true,false,false,null);
//消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
//重写接收消息后要执行的方法
/**
* @param consumerTag 标识消费者
* @param envelope 信封:可以拿到交换机 可以获得消息id
* @param properties 属性值
* @param body 消息内容
* @throws IOException
*/
@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);
}
};
// 监听队列
/**
* 参数
* 1 队列名称
* 2 是否自动回复
* 3 消费方法:接收到消息后需要执行的方法
*/
channel.basicConsume("heyexi",true,defaultConsumer);
System.out.println("发送成功");
} catch (IOException | TimeoutException e)
{
e.printStackTrace();
} finally
{
}
}
}
然后我们启动消费者程序,那么只要生产者发送消息那么消费者都能收到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIvDW3FM-1631801852298)(https://www.heyexi.com/markdown/image-20210816223423300.png)]
我们重新启动生成者
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8Nyt2Ug-1631801852300)(https://www.heyexi.com/markdown/image-20210816223453835.png)]
而且如果我们不关闭连接,这两个程序都是用while(true)
来一直监听的,这就是利用了观察者模式。
还可以从网页后台发送哦
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hcZddLC-1631801852302)(https://www.heyexi.com/markdown/image-20210816235222022.png)]
还是很有趣的
工作模式
-
简单模式:一个生产者,一个消费者
简单模式就是上面的例子,一个生产者给一个消费者发送消息。
-
work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一(消费者彼此竞争成为接收者)
工作模式是一个生产者发送消息,然后多个消费者共同监听同一个队列,但是它有一个特点,就是一个消息不能重复消费,就是生产者发送一个消息,那么只会到其中一个消费者,然后再发送消息时采用轮询的方式再发给另一个。
这里运行两个消费者,然后启动生产者。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AolUv3uj-1631801852304)(https://www.heyexi.com/markdown/image-20210821184103535.png)]
只有其中的一个收到了消息,另一个没有。再次运行生产者另一个消费者才能收到消息。
-
订阅模式:一个生产者发送的消息会被多个消费者获取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nIgfq8dB-1631801852305)(https://www.heyexi.com/markdown/image-20210821192203789.png)]
发布订阅模式消费者只监听它订阅了的队列,中间有个X代表交换机,生产者通过交换机给不同的队列发消息。
现在我们定义一个发布者,然后创建一个交换机,两个队列,再将交换机和队列通过通道进行绑定,最后再弄两个消费者分别监听队列1和队列2.
** * 创建一个生产者 */ public class Producer2 { public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("ip"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("pwd");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //声明对象 /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ //生成两个队列 channel.queueDeclare("q1",true,false,false,null); channel.queueDeclare("q2",true,false,false,null); //声明交换机 /** * 参数: * 1. 交换机名称 * 2. 类型:通过定义的字符串常量声明 * HEADERS: 对应headers工作模式 * DIRECT: 对应路由工作模式 * FANOUT: 对应t发布订阅模式 * TOPIC: 对应topic工作模式 */ channel.exchangeDeclare("ex", BuiltinExchangeType.FANOUT); //绑定交换机和队列 /** * 参数: * 1. 队列名称 * 2. 交换机名称 * 3. 路由key,发布订阅模式中为空 */ channel.queueBind("q1","ex",""); channel.queueBind("q2","ex",""); //发送消息:这时候要指定给交换机发送,而不是给队列发送 for (int i = 0; i < 6; i++) { String msg = "来自发布订阅模式的消息"; channel.basicPublish("ex","",null,msg.getBytes()); } System.out.println("发送成功"); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
然后我们创建两个生成者,这两个生成者需要创建和发布者一样的交换机,然后队列只需要生成自己要监听的队列,然后绑定就行。
/** * 消费者1 */ public class Consumer1 { public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("ip"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("pwd");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //创建交换机 channel.exchangeDeclare("ex",BuiltinExchangeType.FANOUT); /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ channel.queueDeclare("q1",true,false,false,null); //绑定队列 channel.queueBind("q1","ex",""); //消费方法 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ //重写接收消息后要执行的方法 /** * @param consumerTag 标识消费者 * @param envelope 信封:可以拿到交换机 可以获得消息id * @param properties 属性值 * @param body 消息内容 * @throws IOException */ @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("消费者1收到消息:"+msg); } }; // 监听队列1 /** * 参数 * 1 队列名称 * 2 是否自动回复 * 3 消费方法:接收到消息后需要执行的方法 */ channel.basicConsume("q1",true,defaultConsumer); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
/** * 消费者1 */ public class Consumer2 { public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("8.129.54.174"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("kingsley");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //创建交换机 channel.exchangeDeclare("ex",BuiltinExchangeType.FANOUT); /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ channel.queueDeclare("q2",true,false,false,null); //绑定队列 channel.queueBind("q2","ex",""); //消费方法 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ //重写接收消息后要执行的方法 /** * @param consumerTag 标识消费者 * @param envelope 信封:可以拿到交换机 可以获得消息id * @param properties 属性值 * @param body 消息内容 * @throws IOException */ @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("消费者2收到消息:"+msg); } }; // 监听队列1 /** * 参数 * 1 队列名称 * 2 是否自动回复 * 3 消费方法:接收到消息后需要执行的方法 */ channel.basicConsume("q2",true,defaultConsumer); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
然后启动发布者,顺序不重要,因为都声明了交换和队列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrhi9d0p-1631801852306)(https://www.heyexi.com/markdown/image-20210826202550235.png)]
可以看到有12条消息等待发送,也就是有两个队列,每个队列要发送六条,一共就是十二个,我先启动消费者1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afoTmYj4-1631801852307)(https://www.heyexi.com/markdown/image-20210826202710058.png)]
这样消息就只有消费者2的没发送了。
最后我从后台发给消息给队列1,那么就只有监听了队列1的消费者才能接收到。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAIS446f-1631801852309)(https://www.heyexi.com/markdown/image-20210826202920310.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tU0HUa9-1631801852310)(https://www.heyexi.com/markdown/image-20210826202928475.png)]
-
我路由模式:发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换机时需要指定路由key
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4DQD5VX-1631801852312)(https://www.heyexi.com/markdown/image-20210826204245149.png)]
路由模式和发布订阅类似,只是多了一个
routingKey
,发布订阅模式是像工作模式一样发布者发的消息都会发送到每个队列,然后监听队列的多个用户进行轮询获取消息。而路由模式可以通过这个路由key,来指定发送给哪个队列,队列监听了哪个路由key,那么就会接收到那个消息,如上图第二个和第一个都能接受到error
的消息,但是第一个除了这个都不能接受到。我们来实现通过路由模式给不同的队列发送消息。
/** * 创建一个生产者 */ public class ProducerRouter { private static String exchange_name = "exchange001";//路由器名称 private static String q1 = "q1";//队列 private static String q2 = "q2";//队列 private static String key_qq_email = "qq";//路由key给QQ发送 private static String key_yeah_email = "yeah";//路由key给网易邮箱发送 public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("ip"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("pwd");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //声明对象 /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ //生成两个队列 channel.queueDeclare(q1,true,false,false,null); channel.queueDeclare(q2,true,false,false,null); //声明交换机 /** * 参数: * 1. 交换机名称 * 2. 类型:通过定义的字符串常量声明 * HEADERS: 对应headers工作模式 * DIRECT: 对应路由工作模式 * FANOUT: 对应t发布订阅模式 * TOPIC: 对应topic工作模式 */ channel.exchangeDeclare(exchange_name, BuiltinExchangeType.DIRECT); //绑定交换机和队列 /** * 参数: * 1. 队列名称 * 2. 交换机名称 * 3. 路由key,路由模式中一套指定路由Key了 * 这里队列1监听qq和网易邮箱的消息,而队列2只监听网易 * 监听多个路由Key可以传入map */ channel.queueBind(q1,exchange_name,key_qq_email); channel.queueBind(q1,exchange_name,key_yeah_email); channel.queueBind(q2,exchange_name,key_yeah_email); String msg = ""; //发送消息:给QQ邮箱的发消息 msg = "通过路由模式给QQ邮箱发消息"; channel.basicPublish(exchange_name,key_qq_email,null,msg.getBytes()); //发送消息:给挖网易邮箱的发消息 msg = "通过路由模式给网易邮箱发消息"; channel.basicPublish(exchange_name,key_yeah_email,null,msg.getBytes()); System.out.println("发送成功"); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
然后创建两个消费者,一个监听队列1,一个监听队列2.
/** * 消费者1 */ public class ConsumerRouter1 { private static String exchange_name = "exchange001";//路由器名称 private static String q1 = "q1";//队列 private static String key_qq_email = "qq";//路由key给QQ发送 private static String key_yeah_email = "yeah";//路由key给网易邮箱发送 public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("ip"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("pwd");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //创建交换机 channel.exchangeDeclare(exchange_name,BuiltinExchangeType.DIRECT); /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ channel.queueDeclare(q1,true,false,false,null); //绑定队列 channel.queueBind(q1,exchange_name,key_qq_email); //channel.queueBind(q1,exchange_name,key_yeah_email); //消费方法 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ //重写接收消息后要执行的方法 /** * @param consumerTag 标识消费者 * @param envelope 信封:可以拿到交换机 可以获得消息id * @param properties 属性值 * @param body 消息内容 * @throws IOException */ @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("消费者1收到消息:"+msg); } }; // 监听队列1 /** * 参数 * 1 队列名称 * 2 是否自动回复 * 3 消费方法:接收到消息后需要执行的方法 */ channel.basicConsume(q1,true,defaultConsumer); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
/** * 消费者1 */ public class ConsumerRouter2 { private static String exchange_name = "exchange001";//路由器名称 private static String q2 = "q2";//队列 private static String key_yeah_email = "yeah";//路由key给网易邮箱发送 public static void main(String[] args) { //创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接对象的属性 connectionFactory.setHost("ip"); connectionFactory.setUsername("root");//用户名 connectionFactory.setPassword("pwd");//密码 connectionFactory.setPort(5672);//端口 connectionFactory.setVirtualHost("/");//虚拟机 //通过工厂创建连接对象 Connection connection = null; try { connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //创建交换机 channel.exchangeDeclare(exchange_name,BuiltinExchangeType.DIRECT); /** * 参数: * 1 队列名称 * 2 是否持久化:就是重启MQ后是否存在 * 3 是否独占连接,就是连接对象销毁后此队列是否还存在 * 4 是否自动删除,就是空闲不用会自己删除 * 5 通过map设置一些扩展参数 */ channel.queueDeclare(q2,true,false,false,null); //绑定队列 channel.queueBind(q2,exchange_name,key_yeah_email); //消费方法 DefaultConsumer defaultConsumer = new DefaultConsumer(channel){ //重写接收消息后要执行的方法 /** * @param consumerTag 标识消费者 * @param envelope 信封:可以拿到交换机 可以获得消息id * @param properties 属性值 * @param body 消息内容 * @throws IOException */ @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("消费者1收到消息:"+msg); } }; // 监听队列1 /** * 参数 * 1 队列名称 * 2 是否自动回复 * 3 消费方法:接收到消息后需要执行的方法 */ channel.basicConsume(q2,true,defaultConsumer); } catch (IOException | TimeoutException e) { e.printStackTrace(); } finally { } } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzUTKYWe-1631801852314)(https://www.heyexi.com/markdown/image-20210826211109455.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3kPuOSD-1631801852315)(https://www.heyexi.com/markdown/image-20210826211115492.png)]
-
topic模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上,“#”匹配一个词或多个词,“*”只匹配一个词。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0cbNZzCX-1631801852316)(https://www.heyexi.com/markdown/image-20210826211821522.png)]
通配符模式和路由模式是一样的,都是通过路由key进行消息队列发送,不同的是路由模式是将路由key进行相等判断匹配,而通配符是根据通配符进行匹配。
每个路由直接通过.来分隔,通配符有#和
*
,#匹配单个或多个单词,*
匹配一个单词#: com.#可以匹配 com.heyexi com.libai com.heyexi.pojo *: com.*可以匹配 com.heyexi com.libai 不能匹配:com.heyexi.pojo
代码和路由模式一样,唯一要改的就是在绑定队列和路由时的路由Key换成通配符,在发送消息时选择具体的路由Key,它会去匹配上面队列和交换机绑定的通配符模式。哦,还有要改交换机的类型。
-
header模式
header模式与routing不同的地方在于,header模式取消
routingkey
,使用header中的 key/value(键值对)匹配队列。
SpringBoot
集成RabbitMQ
现在使用SpringBoot
来集成一下消息队列,首先添加依赖,因为是基于AMQP
协议,所以需要先添加这个,版本选择自动继承。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
和Springboot
集成的项目都可以在配置文件中配置,那我们这个Rabbit的连接信息也可以在那里配置
server:
port: 8090
spring:
rabbitmq:
host: ip
port: 5672
username: root
password: pwd
virtual-host: /
然后创建一个配置类,用来声明交换机,队列之类的信息。配置就三步:
定义队列、定义交换机、绑定队列和交换机
@Configuration
public class RabbitmqConfig
{
private String q1_name = "queue_q1";
private String q2_name = "queue_q2";
private String ex_name = "exchange_ex";
private String key1 = "qq";
private String key2 = "yeah";
//1.定义队列
@Bean
public Queue q1()
{
return new Queue(q1_name);
}
@Bean
public Queue q2()
{
return new Queue(q2_name);
}
//2.定义交换机
@Bean
public DirectExchange directExchange()
{
return new DirectExchange(ex_name);
}
@Bean //要使用的交换机都可以定义
public FanoutExchange fanoutExchange()
{
return new FanoutExchange(ex_name);
}
//3.绑定交换和队列
//这里注意:不同类型的交换机和队列的绑定方法会有所不同
//然后就是参数名称就是上面的方法名,通过方法名找到上面的队列或者交换机
@Bean
Binding bindingExQueue(Queue q1,DirectExchange directExchange)
{
return BindingBuilder.bind(q1).to(directExchange).with(key1);
}
@Bean
Binding bindingExQueue2(Queue q2,DirectExchange directExchange)
{
return BindingBuilder.bind(q2).to(directExchange).with(key2);
}
}
然后创建生产者,生产者就负责发送消息,发送的方法可以通过amqpTemplate
这个来发送,只是交换机不同里面参数不一样而已,和原始参数是一样的,可以下载源码查看不同的交换机的发送参数这里使用的是路由交换机的发送。
/**
* 生产者
*/
@Component
public class BootRouterProducer
{
@Autowired
private AmqpTemplate amqpTemplate;
@Autowired
private RabbitmqConfig rabbitmqConfig;
/**
* 发送消息方法
*/
public void sendMsg(String msg,String rourtingKey)
{
System.out.println("给"+rourtingKey+"队列发送:");
//发送消息
amqpTemplate.convertAndSend(rabbitmqConfig.directExchange().getName(),rourtingKey,msg);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fyUyEzpd-1631801852318)(https://www.heyexi.com/markdown/image-20210827183608446.png)]
然后定义消费者,消费者很简单,使用RabbitListener
注解指定要监听的队列名,使用RabbitHandler
注解在代表收到生产者的信息时,会执行这个方法,我们可以在这里对数据进行处理,就算程序结束消息没接收完,那么下次重启程序还是会执行这里把消息接受完,这个方法是很重要的。
/**
* 监听Q1队列的消费者
*/
@Component
@RabbitListener(queues = "queue_q1") //这个注解代表监听队列
public class BootConsumerQ1
{
@RabbitHandler //使用这个注解代表监听方法
public void process(String msg)
{
System.out.println("消费者1收到消息:"+msg);
}
}
最后定义一个控制器来发送消息,我们可以在实际项目做做成一个发送消息的方法,那么就是一旦发送就会在消费者方法中收到消息,进行通知用户的的处理。
@RestController
public class SendMsgController
{
@Autowired
private BootRouterProducer bootRouterProducer;
@GetMapping("/sendMsg")
public String sendMsg(String msg,String routingkey)
{
bootRouterProducer.sendMsg(msg,routingkey);
return "ok";
}
}