rabbitMq
一、 MQ的重要性
最近mq越来越火,很多公司在用,很多人在用,其重要性不言而喻。但是如果我让你回答下面的这些问题:
- 我们为什么要用mq?
- 引入mq会多哪些问题?
- 如何解决这些问题?
二、MQ的概述
MQ,Message Queue,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生
产、存储、消费全过程的软件系统,是在消息的传输过程中保存消息的容器,多用于分布式之间进行通信,消息即数据。一般消息的体量不会很大。
小结
- MQ消息队列,存储消息的中间键
- 分布式系统通信两种方式:直接远程调用和借助第三方完成间接通信
- 发送方称为生产者,接收方称为消费者
三、MQ的优势和劣势
优势:
- 应用解耦
- 异步同速
- 削峰填谷
劣势:
- 系统可用性降低
- 系统复杂度提高
- 一致性问题
1.1应用解耦
系统的耦合性越高,容错性就越低,可维护性就越低
]
使用MQ使得应用之间解耦,提升了容错性和可维护性
1.2异步提速
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMftZaCD-1666226283058)(https:/]
一个下单操作时间耗时:20+300+300+300=920ms
用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢了
用户点击完下单按钮后,只需要等待25ms就能得到下单响应(20+5)=25ms
提升用户体验和系统吞吐量(单位时间内处理请求的数目)
1.3削峰填谷
使用了MQ之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被削掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫“填谷”
使用MQ之后,可以提高系统稳定性
- 应用解耦:提高系统容错性和可维护性
- 异步同速:提升用户体验和系统吞吐量
- 削峰填谷:提高系统稳定性
系统的耦合性越高,容错性就越低,可维护性越低
1.4MQ的劣势
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3TY8Nub6-1666225760718)(C:\Users\adminst\AppData\Roaming\Typora\typora-user-images\image-20221009150638139.png)]
- 系统可用性降低
- 系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。
2.2.2 系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
2.2.3 一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?
真实场景和面试当中经常遇到
小结
MQ 有优势也有劣势,那么使用 MQ 需要满足什么条件呢?
生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这 才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
容许短暂的不一致性。
确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本
四、常见的MQ产品
1.1rabbitMQ的简介
2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ 1.0发布。RabbitMQ采用Erlang语言开发。
Erlang语言由Ericson设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
AMQP
AMQP,即advanced Message Queuing Protocol (高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端\中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP规范发布。类比HTTP。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qmBHw2xj-1666225760719)(assets/image-20221014091014389.png)]
rabbitMQ的基础架构图
相互分离 逻辑分区的作用
1.2RabbitMQ中的相关概念
Broker:接收和分发消息的应用,RabbitMQ Server就是Message Broker
Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,/以于网 络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange / queue等
Connection: publisher / consumer 和 broker 之间的 TCP 连接
Channel:如果次访问 RabbitMQ 都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线 程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和 message broker识别 channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection 极大减少了操作系统建立TCP connection的开销
Exchange: message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到 queue 中去。常用类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
Queue:消息最终被送到这里等待consumer取走
Binding: exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到exchange中的查询表中,用于message的分发依据
1.3RabbitMQ提供6种工作模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h50GckBm-1666225760720)(C:\Users\adminst\AppData\Roaming\Typora\typora-user-images\image-20221011202609669.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nm14VJhV-1666225760720)(C:\Users\adminst\AppData\Roaming\Typora\typora-user-images\image-20221011202642162.png)]
- 简单模式
- work queues
- publish\subscribe发布与订阅模式
- Routing路由模式
- Topics主题模式
- RPC远程调用模式(远程调用,不太算MQ)
小结
- rabbitMQ是基于AMQP协议使用的Erlang语言开发的一款消息队列产品
- RabbitMQ提供了6中工作模式
- AMQP是协议,类比HTTP
- JMS是API规范接口,类比JDBC
五、RabbitMq的安装
1.安装准备工作
这里通过官网下载需要的版本:RabbitMQ官方网址
鉴于官网访问下载比较慢,贴一个云盘地址:百度云盘地址
进入官网向下拉,找到 Download + Installation 下载+安装,点击进入。
在新页面找到右侧,Install Windows 安装windows版本
然后页面下拉,找到 Dependencies 依赖关系
选择otp_win64_24.1.7.exe 我的系统是64位的,所以下载win64
这里笔者强调一下。我安装的是最新版本。如果你们觉得最新版可能存在未知bug或不兼容问题,可以选择自己想要下载的对应版本即可。
这里我截图一部分版本参照表,其他的可以自行查看
2.开始安装
第一步:找到我们下载的软件位置。
第二步:先安装otp_win64_24.1.7.exe
第三步:鼠标右键以管理员方式运行
第四步:接着选取要安装的路径,然后一路傻瓜式安装 next 下一步,安装即可。
【注意】不要安装在中文或带空格的文件路径下
第五步:配置系统环境变量
右键此电脑 - 属性 - 高级系统设置 - 环境变量
接着打开 - 此电脑(文件资源管理器) 找到刚刚我们安装的 erl - 24.1.7 文件 bin 目录下,复制路径 ctrl+c
切换窗口到环境变量,找到系统变量 path - 编辑
新建 - ctrl + v 粘贴刚才我们复制的路径,然后三次确定,关闭环境变量窗口
第六步:安装 RabbitMQ
右键管理员运行,然后选择安装路径,接着一路 next 下一步,遇到弹窗点允许,没有弹窗则无视。
【注意】不要安装在中文或带空格的文件路径下
第七步:安装完成后找到安装文件路径,找到 sbin 目录下,全选路径 输入 cmd
打开cmd命令窗口。
运行下面命令,回车运行。
rabbitmq-plugins enable rabbitmq_management
由于我已经安装过了,所以贴一张。运行成功的图片
第八步:打开任务资源管理器。win11 快捷键 Ctrl+Shift+Esc,找到rabbitmq服务右键重新启动。
3.结束安装
打开浏览器。访问 http://127.0.0.1:15672
出现管理页面:
账号:guest
密码:guest
登录成功后。进入下面页面即代表安装成功。至此大功告成。是不是很简单!
六、RabbitMq管理台的使用
七、RabbitMq快速入门-生产者 消费者
1、HelloWorld模式-简单队列模式
需求:使用简单的模式完成消息传递
- 步骤
- 创建工程(生产者,消费者)
- 分别添加依赖
- 编写生产者发送消息
- 编写消费者接收消息
创建消息的⽣产者
编写⽣产者
package com.chen.producer;
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 Produce_WorkQueues {
//hello word 的生产者
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/**
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 1.queue:队列名称
* 2.durable:是否持久化,当MQ重启之后,还在
* 3.exclusive:
* *是否独占,只能有一个消费者监听这个队列
* *当connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除,当没有Consumer时,自动删除
* 5.arguments:参数
*/
//如果没有一个名叫hellomq的队列,则会创建该队列,如果有,则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
* 参数:
* 1.exchange:交换机名称。简单模式下交换机会使用默认的“”
* 2.routingKey:路由名称
* 3.props:配置信息
* 4.body:发送消息数据
*/
for (int i = 1; i <=10 ; i++) {
String body = i+"hello_mq~~~";
//6.发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
创建消息的消费者
编写消费者
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_HelloWord {
//hello word 的消费者
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/**
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 1.queue:队列名称
* 2.durable:是否持久化,当MQ重启之后,还在
* 3.exclusive:
* *是否独占,只能有一个消费者监听这个队列
* *当connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除,当没有Consumer时,自动删除
* 5.arguments:参数
*/
//如果没有一个名叫hellomq的队列,则会创建该队列,如果有,则不会创建
channel.queueDeclare("Hello_MQ",true,false,false,null);
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:"+consumerTag);
System.out.println("Exchange:"+envelope.getExchange());
System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
System.out.println("properties:"+properties);
}
};
channel.basicConsume("Hello_MQ",true,consumer);
//关闭资源? 不要关闭
}
}
简单队列的问题:
当多个消费者消费同⼀个队列时。这个时候rabbitmq的公平调度机制就开启了,于是,⽆论消费者的消费能⼒如何,每个消费者都能公平均分到相同数量的消息,⽽不能出现能者多劳的情况。
2.work 队列模式: 能者多劳模式
生产者
消费者先声明⼀次只接收⼀条消息: channel.basicQos(1)
消费者关闭⾃动ack
package com.chen.producer;
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 Produce_WorkQueues {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/**
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 1.queue:队列名称
* 2.durable:是否持久化,当MQ重启之后,还在
* 3.exclusive:
* *是否独占,只能有一个消费者监听这个队列
* *当connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除,当没有Consumer时,自动删除
* 5.arguments:参数
*/
//如果没有一个名叫hellomq的队列,则会创建该队列,如果有,则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
* 参数:
* 1.exchange:交换机名称。简单模式下交换机会使用默认的“”
* 2.routingKey:路由名称
* 3.props:配置信息
* 4.body:发送消息数据
*/
for (int i = 1; i <=10 ; i++) {
String body = i+"hello_mq~~~";
//6.发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
消费者1
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/**
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 1.queue:队列名称
* 2.durable:是否持久化,当MQ重启之后,还在
* 3.exclusive:
* *是否独占,只能有一个消费者监听这个队列
* *当connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除,当没有Consumer时,自动删除
* 5.arguments:参数
*/
//如果没有一个名叫hellomq的队列,则会创建该队列,如果有,则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
// System.out.println("properties:"+properties);
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源? 不要关闭
}
}
消费者2
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueues2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue
/**
* queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
* 1.queue:队列名称
* 2.durable:是否持久化,当MQ重启之后,还在
* 3.exclusive:
* *是否独占,只能有一个消费者监听这个队列
* *当connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除,当没有Consumer时,自动删除
* 5.arguments:参数
*/
//如果没有一个名叫hellomq的队列,则会创建该队列,如果有,则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
// System.out.println("properties:"+properties);
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源? 不要关闭
}
}
3.发布订阅模式-fanout
对于之前的队列模式,是没有办法解决⼀条消息同时被多个消费者消费。于是使⽤发布订阅模式来实现。
关键步骤:声明交换机、把消息发送到交换机上
生产者
package com.chen.producer;
import com.rabbitmq.client.BuiltinExchangeType;
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_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/**
* 参数
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,boolean internal, Map<String, Object> arguments)
* 1.exchange:交换机名称
* 2.type:交换机类型
* DIRECT("direct"), :定向
* FANOUT("fanout"), :扇形(广播),发送消息到每一个与之绑定队列
* TOPIC("topic"), :通配符的形式
* HEADERS("headers");:参数匹配
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般都是false
* 6.arguments:参数
*/
String exchangeName = "test_fanout";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//6.创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 参数:
* 1.queue:队列名称
* 2.exchange:交换机的名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey设置为“ ”
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
//8.发送消息
String body = "日志消息:我调用了findall的方法...日志级别为info...";
channel.basicPublish(exchangeName,"",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者
关键动作:创建队列 创建交换机 把队列绑定在交换机上 让消费者监听队列
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
// System.out.println("properties:"+properties);
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源? 不要关闭
}
}
4.routing模式-direct
关键动作:
在⽣产者发送消息时指明routing-key
在消费者声明队列和交换机的绑定关系时,指明routing-key
生产者
package com.chen.producer;
import com.rabbitmq.client.BuiltinExchangeType;
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_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/**
* 参数
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,boolean internal, Map<String, Object> arguments)
* 1.exchange:交换机名称
* 2.type:交换机类型
* DIRECT("direct"), :定向
* FANOUT("fanout"), :扇形(广播),发送消息到每一个与之绑定队列
* TOPIC("topic"), :通配符的形式
* HEADERS("headers");:参数匹配
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般都是false
* 6.arguments:参数
*/
String exchangeName = "test_direct";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//6.创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 参数:
* 1.queue:队列名称
* 2.exchange:交换机的名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey设置为“ ”
*/
//1.队列1的绑定 error
channel.queueBind(queue1Name,exchangeName,"error");
//2.队列2的绑定,info error warning
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
//8.发送消息
String body = "日志消息:我调用了delete的方法...日志级别为error...";
channel.basicPublish(exchangeName,"error",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.237");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
// System.out.println("properties:"+properties);
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源? 不要关闭
}
}
5.topics模式
在routing模式的基础上,对routing-key使⽤了通配符,提⾼了匹配的范围,增加了可玩性
生产者
package com.chen.producer;
import com.rabbitmq.client.BuiltinExchangeType;
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_Topics {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.236");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/**
* 参数
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,boolean internal, Map<String, Object> arguments)
* 1.exchange:交换机名称
* 2.type:交换机类型
* DIRECT("direct"), :定向
* FANOUT("fanout"), :扇形(广播),发送消息到每一个与之绑定队列
* TOPIC("topic"), :通配符的形式
* HEADERS("headers");:参数匹配
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用,一般都是false
* 6.arguments:参数
*/
String exchangeName = "test_topic";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//6.创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 参数:
* 1.queue:队列名称
* 2.exchange:交换机的名称
* 3.routingKey:路由键,绑定规则
* 如果交换机的类型为fanout,routingKey设置为“ ”
*/
//routing key 系统的名称.日志的级别
//需求:所有error级别的日志存入数据库,所有order系统的日志存入数据库
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
//8.发送消息
String body = "日志消息:我调用了findall的方法...日志级别为info...";
channel.basicPublish(exchangeName,"goods.error",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.236");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
System.out.println("将日志信息存入数据库中.....");
// System.out.println("properties:"+properties);
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源? 不要关闭
}
}
package com.chen.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.236");//ip默认是localhost
factory.setPort(5672);//端口,默认是5672
// factory.setVirtualHost("/testhost");//虚拟机的默认值/
factory.setUsername("admin");//用户名,密码默认都是guest
factory.setPassword("admin");
//3.创建连接connection
Connection connection= factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
/**
* basicConsume(String queue, boolean autoAck, Consumer callback)
* 参数:
* 1.queue:队列名称
* 2.autoAck是否自动确认
* 3.callback回调对象
*/
//6.接收消息
Consumer consumer = new DefaultConsumer(channel){
/**
* 回调方法,当收到消息后,会自动执行该方法
* @param consumerTag :标识
* @param envelope :获取一些信息,交换机的信息,路由key....
* @param properties :配置信息
* @param body :真是的数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
System.out.println("body:"+new String(body));
System.out.println("将日志信息打印到控制台.....");
// System.out.println("properties:"+properties);
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源? 不要关闭
}
}
八、spring整合RabbitMQ
1.1引入依赖
创建生产者和消费者工程(相同的步骤)
<dependencies>
<!--spring 上下文-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<!-- spring 整合rabbit-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<!-- 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译插件包-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
1.2配置文件配置rabbitmq(rabbitmq.properties
)
rabbitmq.host=192.168.0.236
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=chen
1.3生产者
创建在resources包下创建spring-rabbitmq-producer.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:/rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory 也就是工厂加载配置文件里面的内容 -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<!--封装之后的admin的工厂-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除,最后一个消费者和该队列断开连接后自动删除
durable:是否持久化
exclusive;是否独占
-->
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- <rabbit:direct-exchange name="aa">-->
<!-- <rabbit:bindings>-->
<!-- <!–direct类型的交换机绑定队列 key:路由key queue:队列名称–>-->
<!-- <rabbit:binding queue="spring_queue" key="xxx"></rabbit:binding>-->
<!-- </rabbit:bindings>-->
<!-- </rabbit:direct-exchange>-->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="chen.*" queue="spring_topic_queue_star"/>
<rabbit:binding pattern="chen.#" queue="spring_topic_queue_well"/>
<rabbit:binding pattern="ghh.#" queue="spring_topic_queue_well2"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
1.4hello word模式
在测试类里面
//运行器
@RunWith(SpringJUnit4ClassRunner.class)
//加载配置文件
@ContextConfiguration(locations = "classpath:spring-rabbit-producer.xml")
public class TestProducer {
//注入RabbitTemplate
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWord(){
//发送消息
//spring_queue是刚刚的spring-rabbitmq-producer.xml文件定义的
rabbitTemplate.convertAndSend("spring_queue","hello_springmq");
}
1.5fanout模式
发送完之后两个队列都会有消息
/**
* 发送fanout消息
*/
@Test
public void testFanout() {
//2.发送消息
rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout...");
}
1.6发送topic的消息
/**
* 发送topic的消息
*/
@Test
public void testTopic() {
//2.发送消息
rabbitTemplate.convertAndSend("spring_topic_exchange","chen.haha.lala","spring topic...");
}
2.1消费者
创建在resources包下创建spring-rabbitmq-consumer.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id="springQueueListener" class="com.chen.SpringQueueListener"/>
<!-- <bean id="fanoutListener1" class="com.itheima.rabbitmq.listener.FanoutListener1"/>-->
<!-- <bean id="fanoutListener2" class="com.itheima.rabbitmq.listener.FanoutListener2"/>-->
<!-- <bean id="topicListenerStar" class="com.itheima.rabbitmq.listener.TopicListenerStar"/>-->
<!-- <bean id="topicListenerWell" class="com.itheima.rabbitmq.listener.TopicListenerWell"/>-->
<!-- <bean id="topicListenerWell2" class="com.itheima.rabbitmq.listener.TopicListenerWell2"/>-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
<!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>-->
<!-- <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>-->
<!-- <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>-->
<!-- <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>-->
<!-- <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
</rabbit:listener-container>
</beans>
2.1监听器
1按照定义的路径写监听器 com.chen.SpringQueueListener
2实现MessageListener
接口
3输入监听内容
package com.chen;
import org.junit.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
//打印消息
System.out.println(new String(message.getBody()));
}
}
4直接用测试类来加载配置文件
package com.chen;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbit-consumer.xml")
public class ConsumerTest {
@Test
public void test1() {
boolean flag = true;
while (true){
}
}
}
九、spring boot整合Rabbit MQ
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.编写配置文件
server:
port: 8090
spring:
rabbitmq:
host: 172.16.253.8
port: 5672
username: xiaoming
password: 123456
virtual-host: java2007
3.使用发布订阅模式
1.1编写消费者
编写配置类
package com.qf.myspringbootconsumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitConfig {
private static String EXCHANGE_NAME = "my_boot_fanout_exchange";
private static String QUEUE_NAME = "my_boot_fanout_queue1";
/*
声明交换机
*/
@Bean
public FanoutExchange exchange(){
return new FanoutExchange(EXCHANGE_NAME,true,false);
}
/*
声明队列
*/
@Bean
public Queue queue(){
return new Queue(QUEUE_NAME,true,false,false);
}
/*
声明绑定关系
*/
@Bean
public Binding queueBinding(Queue queue,FanoutExchange fanoutExchange){
return BindingBuilder.bind(queue).to(fanoutExchange);
}
}
编写消费消息的方法
关键:使⽤该注解来指定监听的队列@RabbitListener(queues =“my_boot_fanout_queue”)
package com.qf.myspringbootconsumer;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MyConsumer {
/*
监听队列:当队列中有消息,则监听器工作,处理接收到的消息
*/
@RabbitListener(queues = "my_boot_fanout_queue1")
public void process(Message message){
byte[] body = message.getBody();
System.out.println("接收到的消息:"+new String(body));
}
}
1.2编写生产者
编写配置类
package com.qf.my.spring.boot.producer.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitConfig {
private static String EXCHANGE_NAME = "my_boot_fanout_exchange";
/**
* 声明交换机
* @return
*/
@Bean
public FanoutExchange exchange(){
return new FanoutExchange(EXCHANGE_NAME,true,false);
}
}
使⽤RabbitTemplate发送消息
package com.qf.my.spring.boot.producer;
import com.qf.my.spring.boot.producer.config.MyRabbitConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MySpringBootProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void testSendMessage(){
String message = "hello spring boot message";
rabbitTemplate.convertAndSend("my_boot_fanout_exchange","",message);
}
}
4.使用topic模式
topic模式相⽐发布订阅模式,多了routing-key的使⽤
1.1调整消费者配置类
package com.qf.my.spring.boot.topic.consumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyRabbitConfig {
/*
声明队列、交换机、绑定关系(routing-key)
*/
private static String QUEUE_NAME = "my_boot_topic_queue";
private static String EXCHANGE_NAME = "my_boot_topic_exchange";
//声明队列
@Bean
public Queue queue(){
return new Queue(QUEUE_NAME,true,false,false);
}
//声明交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange(EXCHANGE_NAME,true,false);
}
//声明绑定关系
@Bean
public Binding queueBinding(Queue queue,TopicExchange topicExchange){
return BindingBuilder.bind(queue).to(topicExchange).with("product.*");
}
}
1.2编写生产者
调整生产者的配置
package com.qf.my.spring.boot.topic.producer.config;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Configuration
public class MyRabbitConfig {
private static String EXCHANGE_NAME = "my_boot_topic_exchange";
//声明交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange(EXCHANGE_NAME,true,false);
}
}
发布时携带routingkey
package com.qf.my.spring.boot.topic.producer;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
class MySpringBootTopicProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void testSendMessage() {
String message = "hello topic message";
rabbitTemplate.convertAndSend("my_boot_topic_exchange","product.add",message);
}
}
5.手动ACK的实现
在配置⽂件中添加⼿动ack的配置
server:
port: 8091
spring:
rabbitmq:
host: 192.168.0.236
port: 5672
username: admin
password: admin
virtual-host: chen
listener:
simple:
acknowledge-mode: manual
在消费者中进⾏⼿动ack
package com.qf.my.spring.boot.topic.consumer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyConsumer {
@RabbitListener(queues = "my_boot_topic_queue")
public void process(Message message, Channel channel) throws IOException {
String id = message.getMessageProperties().getHeader("spring_returned_message_correlation");
//if(!redisTemplate.ifAbsent(id)){ return "消息已被消费"}
System.out.println(message.toString());
//手动ack,告知broker要签收的消息的id(deliveryTag)
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
十、RabbitMQ的高级特性
1.消息的可靠性投递
1.通过confirm机制保证⽣产者消息能够投递到MQ
1.1通过confirm机制保证⽣产者消息能够投递到MQ
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onblaw1d-1666225760722)(C:\Users\adminst\AppData\Roaming\Typora\typora-user-images\image-20221012161010403.png)]
- 在spring项⽬中做confirm
package com.qf.producer.confirm;
import com.qf.producer.util.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Confirm机制
* @author Thor
* @公众号 Java架构栈
*/
public class MyProducer {
//定义交换机的名称
private static String EXCHANGE_NAME = "my_topic_exchange";
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//开启confirm机制
channel.confirmSelect();
//设置confirm的监听器
channel.addConfirmListener(new ConfirmListener() {
//当消息被broker签收了,会回调此方法
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息已经成功投递");
}
//当消息没有被broker签收了,会回调此方法
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//开启重试机制,重试达到阈值 则人工介入。
System.out.println("消息投递失败");
}
});
//设置return机制
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
//当消息没有到达队列会调用此方法
System.out.println("消息没有到达队列");
}
});
String message = "hello confirm message";
//如果要开启return,则mandatory必须设置成true
channel.basicPublish(EXCHANGE_NAME,"add",true,null,message.getBytes(StandardCharsets.UTF_8));
}
}
- 在springboot中如何实现步骤一:
步骤一:修改生产者的配置
server:
port: 9090
spring:
rabbitmq:
host: 172.16.253.8
port: 5672
username: xiaoming
password: 123456
virtual-host: java2007
publisher-confirm-type: correlated
publisher-confirm-type:有三种配置:
- simple:简单的执⾏ack的判断;在发布消息成功后使⽤rabbitTemplate调⽤waitForConfirms或waitForConfirmsOrDie⽅法等待broker节点返回发送结果,根据返回结果来判断下⼀步的逻辑。但是要注意的是当waitForConfirmsOrDie⽅法如果返回false则会关闭channel
- correlated: 执⾏ack的时候还会携带数据(消息的元数据);
- none: 禁⽤发布确认模式, 默认的
步骤⼆:编写⼀个ConfirmCallback的实现类(监听器),并注⼊到RabbiTemplate
package com.qf.my.spring.boot.topic.producer.confirm;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Objects;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//把监听器注入到RabbitTemplate
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//获得消息的id
String id = "";
if(Objects.nonNull(correlationData)){
id = correlationData.getId();
}
if(ack){
//消息投递成功
System.out.println("消息投递成功,id:"+id);
}else{
//消息投递失败,存入到缓存中,通过定时任务,定时重发。
System.out.println("消息投递失败,原因:"+cause);
}
}
/**
* 当消息没有传递到队列的时候,回调的方法
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息:"+new String(message.getBody())+",没有成功投递到队列");
}
}
2.通过return机制保证消息在rabbitmq中能够****成功的投递到队列⾥
⽣产者将消息投递到mq的交换机上——Confirm机制来保证的。
如果交换机没办法将消息投递到队列上,就可以通过Return机制来进⾏重试。
1.在spring项⽬中
package com.qf.producer.confirm;
import com.qf.producer.util.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* Confirm机制
* @author Thor
* @公众号 Java架构栈
*/
public class MyProducer {
//定义交换机的名称
private static String EXCHANGE_NAME = "my_topic_exchange";
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//开启confirm机制
channel.confirmSelect();
//设置confirm的监听器
channel.addConfirmListener(new ConfirmListener() {
//当消息被broker签收了,会回调此方法
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息已经成功投递");
}
//当消息没有被broker签收了,会回调此方法
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//开启重试机制,重试达到阈值 则人工介入。
System.out.println("消息投递失败");
}
});
//设置return机制
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
//当消息没有到达队列会调用此方法
System.out.println("消息没有到达队列");
}
});
String message = "hello confirm message";
//如果要开启return,则mandatory必须设置成true
channel.basicPublish(EXCHANGE_NAME,"add",true,null,message.getBytes(StandardCharsets.UTF_8));
}
}
注意,如果要开启return机制的话。需要把mandatory设置成true。
2.在springboot中使⽤
步骤一:修改配置文件
server:
port: 9002
spring:
rabbitmq:
host: 192.168.0.236
username: admin
password: admin
virtual-host: chen
port: 5672
# 开启confirm simple:简单的执⾏ack的判断;correlated: 执⾏ack的时候还会携带数据;none: 不ack 默认的
publisher-confirm-type: correlated
# 开启return机制
publisher-returns: true
步骤⼆:在监听类中实现RabbitTemplate.ReturnCallback接⼝
package com.qf.my.spring.boot.topic.producer.confirm;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Objects;
/**
* @author Thor
* @公众号 Java架构栈
*/
@Component
public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//把监听器注入到RabbitTemplate
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//获得消息的id
String id = "";
if(Objects.nonNull(correlationData)){
id = correlationData.getId();
}
if(ack){
//消息投递成功
System.out.println("消息投递成功,id:"+id);
}else{
//消息投递失败,存入到缓存中,通过定时任务,定时重发。
System.out.println("消息投递失败,原因:"+cause);
}
}
/**
* 当消息没有传递到队列的时候,回调的方法
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息:"+new String(message.getBody())+",没有成功投递到队列");
}
}
3.⼿动ack**、nack、reject的区别**
1 .不做任何的ack
RabbitMQ会把消息标记成unacked,此时mq是在等待消费者进⾏ack,如果消费者失
去了会话,此时消息会重新回到ready状态,被其他消费者消费。
2.ack
确认签收,之后消息会从队列中剔除。
3.reject
reject就是拒绝此条消息。
reject⼀次只⽀持处理⼀条消息。消息被拒绝掉之后,并且requeue设置成了false,
将会进⼊到死信队列中。如果requeue设置成true,将会重回队列,但是这种情况很
少使⽤。
4.nack
nack和reject相同,只是nack⽀持批量处理多条消息。
package com.qf.consumer.acknack;
import com.qf.consumer.util.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* topic的消费者
* @author Thor
* @公众号 Java架构栈
*/
public class MyConsumer1 {
//交换机的名称
private static String EXCHANGE_NAME = "my_topic_exchange";
//队列的名称
private static String QUEUE_NAME = "my_topic_queue_1";
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"product.#");
//创建消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("product.# 消费者:"+new String(body));
//手动ack
//channel.basicAck(envelope.getDeliveryTag(),false);
//nack
// channel.basicNack(envelope.getDeliveryTag(),false,false);
channel.basicReject(envelope.getDeliveryTag(), false);
}
};
//让消费者监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
5.消息元数据的封装
- 生产者端
package com.qf.producer.properties;
import com.qf.producer.util.RabbitUtil;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MyProducer {
//定义交换机的名称
private static String EXCHANGE_NAME = "my_topic_exchange";
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//1.声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//创建消息的元数据
Map<String,Object> map = new HashMap<>();
map.put("name","zhangsan");
map.put("age",20);
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2) //消息是否支持持久化:1不支持,2支持
.messageId(UUID.randomUUID().toString()) //定义消息的业务id
.expiration("10000") //定义消息的过期时间
.headers(map)
.build();
//2.发送消息
channel.basicPublish(EXCHANGE_NAME,"product.add.one",properties,"hello topic".getBytes(StandardCharsets.UTF_8));
System.out.println("消息已发送");
//3.关闭连接
channel.close();
connection.close();
}
}
- 消费者端
package com.qf.consumer.properties;
import com.qf.consumer.util.RabbitUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
public class MyConsumer1 {
//交换机的名称
private static String EXCHANGE_NAME = "my_topic_exchange";
//队列的名称
private static String QUEUE_NAME = "my_topic_queue_1";
public static void main(String[] args) throws Exception {
Connection connection = RabbitUtil.getConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//绑定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"product.#");
//创建消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("product.# 消费者:"+new String(body));
Map<String, Object> map = properties.getHeaders();
System.out.println(map.get("name"));
System.out.println(properties.getMessageId());
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
//nack
// channel.basicNack(envelope.getDeliveryTag(),false,false);
}
};
//让消费者监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
}
}
十一、消息的重复消费问题
1.什么是幂等性
幂等性:多次操作造成的结果是⼀致的。对于⾮幂等的操作,幂等性如何保证?——使⽤分布式锁。
在请求⽅式中的幂等性的体现
- get:多次get 结果是⼀致的
- post:添加,⾮幂等
- put:修改:幂等,根据id修改
- delete:根据id删除,幂等
对于⾮幂等的请求,我们在业务⾥要做幂等性保证。
在消息队列中的幂等性体现
消息队列中,很可能⼀条消息被冗余部署的多个消费者收到,对于⾮幂等的操作,
⽐如⽤户的注册,就需要做幂等性保证,否则消息将会被重复消费。使⽤分布式锁
解决幂等性问题
2.业务代码中实现幂等性
1、⽣产者端修改配置⽂件
server:
port: 9002
spring:
rabbitmq:
host: 192.168.0.236
username: admin
password: admin
virtual-host: chen
port: 5672
# 开启confirm simple:简单的执⾏ack的判断;correlated: 执⾏ack的时候还会携带数据;none: 不ack 默认的
publisher-confirm-type: correlated
# 开启return机制
publisher-returns: true
2、⽣产者端传递业务id
@Test
public void testSendMessage(){
//业务id
String id = UUID.randomUUID().toString();
//封装了业务id的消息元数据
CorrelationData correlationData = new CorrelationData(id);
//发送消息,并且携带消息的业务id
rabbitTemplate.convertAndSend("my_boot_topic_exchange",
"product.add",
"hello message",
correlationData
);
}
3、消费者端进⾏业务逻辑判断
/**
* 消费端的幂等性的实现
*/
@RabbitListener(queues = "my_boot_topic_queue")
public void processByMSG(Message message,Channel channel)
throws IOException {
//如何获得消息的业务id
String messageId =
message.getMessageProperties().getHeader("spring_returned_message_
correlation");
//设置分布式锁
Boolean lock =
redisTemplate.opsForValue().setIfAbsent(messageId, 1,100000,
TimeUnit.MILLISECONDS);
if(lock){
//做消费
System.out.println("添加⽤户成功");
//⼿动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),
false);
}else{
//不做消费
System.out.println("已重复消费");
channel.basicReject(message.getMessageProperties().getDeliveryTag
(),false);
}
}
其中,message的请求头中的这两个键值对分别为:
- spring_listener_return_correlation:该属性是⽤来确定消息被退回时调⽤哪个监听器
- spring_returned_message_correlation:该属性是指退回待确认消息的唯⼀标识
十二、死信队列——“延迟”队列
1.死信队列的介绍
死信队列 ,让⼀条消息,在满⾜⼀定的条件下,成为死信,会被发送到另⼀个交换机上,再被消费。 这个过程就是死信队列的作⽤。死信队列就可以做出“延迟”队列的效果。⽐如,在订单超时未⽀付 ,将订单状态改成“已取消”,这个操作就可以使⽤死信队列来完成。设置消息的超时时间,当消息超时则消息成为死信,于是通过监听死信队列的消费者来做取消订单的动作。
要掌握两个知识:
-
消息如何成为死信? 成为死信的条件
-
怎样创建死信队列,完成死信队列的效果
2.消息成为死信的条件
-
消息被拒签,并且没有重回队列,消息将成为死信。
-
消息过期了,消息将成为死信。
-
队列⻓度有限,存不下消息了,存不下的消息将会成为死信。
3.创建死信队列
关键点:让正常的队列,绑定上死信交换机即可。注意:这个死信交换机实际上也是⼀个正常交换机。
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class MyProducer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.236");//ip默认的话是localhost
factory.setPort(5672);//端口号,默认就是5672
factory.setUsername("admin");
factory.setPassword("admin");//默认的话是guest
factory.setVirtualHost("chen");//虚拟机默认的话是/
//3.创建连接connection
Connection connection = factory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
String nomalExchangeName = "chen.nomal.exchange";
//创建交换机
channel.exchangeDeclare(nomalExchangeName,"topic",true,false,null);
//创建消息
for (int i = 0; i <10 ; i++) {
String orderId = UUID.randomUUID().toString();
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.expiration("10000")
.messageId(orderId)
.build();
String message = "订单对象";
//向channel中发消息
channel.basicPublish(nomalExchangeName,"chen.dlx.add",properties,message.getBytes());
System.out.println("订单"+orderId+"已创建,等待支付");
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class MyDlxConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
//1.设置连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.0.236");//ip默认的话是localhost
factory.setPort(5672);//端口号,默认就是5672
factory.setUsername("admin");
factory.setPassword("admin");//默认的话是guest
factory.setVirtualHost("chen");//虚拟机默认的话是/
//3.创建connection连接
Connection connection = factory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
//声明普通交换机,普通队列,死信交换机、死信队列、建立他们之间的关系
String nomalExchangeName = "chen.nomal.exchange";
String exchangetype = "topic";
String nomalQueueName = "chen.nomal.queue";
String routingkey = "chen.dlx.*";
//声明死信队列
String dlxExchangeName = "chen.dlx.exchange";
String dlxQueueName = "chen.dlx.queue";
//建立他们之间的关系
channel.exchangeDeclare(nomalExchangeName,exchangetype,true,false,null);
Map<String,Object>queueArgs = new HashMap<>();
queueArgs.put("x-dead-letter-exchange",dlxExchangeName);
queueArgs.put("x-max-length",4);
channel.queueDeclare(nomalQueueName,true,false,false,queueArgs);
channel.queueBind(nomalQueueName,nomalExchangeName,routingkey);
//创建死信队列
channel.exchangeDeclare(dlxExchangeName,exchangetype,true,false,null);
channel.queueDeclare(dlxQueueName,true,false,false,null);
channel.queueBind(dlxQueueName,dlxExchangeName,"#");
//创建一个消费死信队列的消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String messageId = properties.getMessageId();
//根据订单id,去数据库更新该订单的状态已取消
System.out.println("当前订单"+messageId+"已取消");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(dlxQueueName,false,consumer);
//拒绝签收消息,让消息成为死信
// Consumer consumer1= new DefaultConsumer(channel){
// @Override
// public void handleDelivery(String consumerTag,
// Envelope envelope,
// AMQP.BasicProperties properties,
// byte[] body)
// throws IOException
// {
// System.out.println("拒绝签收消息");
// //注意一定是不能重回队列
// channel.basicReject(envelope.getDeliveryTag(),false);
//
// }
// };
}
}
Connection connection = factory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
//声明普通交换机,普通队列,死信交换机、死信队列、建立他们之间的关系
String nomalExchangeName = "chen.nomal.exchange";
String exchangetype = "topic";
String nomalQueueName = "chen.nomal.queue";
String routingkey = "chen.dlx.*";
//声明死信队列
String dlxExchangeName = "chen.dlx.exchange";
String dlxQueueName = "chen.dlx.queue";
//建立他们之间的关系
channel.exchangeDeclare(nomalExchangeName,exchangetype,true,false,null);
Map<String,Object>queueArgs = new HashMap<>();
queueArgs.put("x-dead-letter-exchange",dlxExchangeName);
queueArgs.put("x-max-length",4);
channel.queueDeclare(nomalQueueName,true,false,false,queueArgs);
channel.queueBind(nomalQueueName,nomalExchangeName,routingkey);
//创建死信队列
channel.exchangeDeclare(dlxExchangeName,exchangetype,true,false,null);
channel.queueDeclare(dlxQueueName,true,false,false,null);
channel.queueBind(dlxQueueName,dlxExchangeName,"#");
//创建一个消费死信队列的消费者
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String messageId = properties.getMessageId();
//根据订单id,去数据库更新该订单的状态已取消
System.out.println("当前订单"+messageId+"已取消");
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(dlxQueueName,false,consumer);
//拒绝签收消息,让消息成为死信
// Consumer consumer1= new DefaultConsumer(channel){
// @Override
// public void handleDelivery(String consumerTag,
// Envelope envelope,
// AMQP.BasicProperties properties,
// byte[] body)
// throws IOException
// {
// System.out.println(“拒绝签收消息”);
// //注意一定是不能重回队列
// channel.basicReject(envelope.getDeliveryTag(),false);
//
// }
// };
}
}