rabbitMQ入门
一.AMPQ消息协议
AMQP工作流程
1、发布者发布消息,经由交换机。交换机根据路由规则将收到的消息分发给与该交换机绑定的队列。最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
2、发布者、交换机、队列、消费者都可以有多个。同时因为 AMQP 是一个网络协议,所以这个过程中的发布者,消费者,消息代理 可以分别存在于不同的设备上。
3、发布者发布消息时可以给消息指定各种消息属性(Message Meta-data)。有些属性有可能会被消息代理(Brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。
4、从安全角度考虑,网络是不可靠的,又或是消费者在处理消息的过程中意外挂掉,这样没有处理成功的消息就会丢失。基于此原因,AMQP 模块包含了一个消息确认(Message Acknowledgements)机制:当一个消息从队列中投递给消费者后,不会立即从队列中删除,直到它收到来自消费者的确认回执(Acknowledgement)后,才完全从队列中删除。
5、在某些情况下,例如当一个消息无法被成功路由时(无法从交换机分发到队列),消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。
二.RabbitMQ常用的几种消息模型
第一种模型(HelloWorld)
P代表生产者用来生产消息,发送给消费者C,中间的共色部分代表消息队列,用来缓存消息。
首先导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
工具类 RabbitmqUtil.java
public class RabbitmqUtil {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
/**
* 配置连接端口,ip,用户名和密码
*/
static {
//绑定RabbitMQ主机地址
connectionFactory.setHost("192.168.1.6");
//绑定端口
connectionFactory.setPort(5672);
//输入用户名密码
connectionFactory.setUsername("rabbit");
connectionFactory.setPassword("rabbit");
}
/**
* 获取连接对象
* @return
*/
public static Connection getConnection() {
try {
return connectionFactory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭资源
* @param channel
* @param connection
*/
public static void closeConnAndChannel(Channel channel,Connection connection){
try {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
生产者代码
Provider.java
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
//queueDeclare第一个参数是队列名称,第二个是是否持久化,如果是true,队列持久化,但是队列内容的持久化需要在basicProperties设置
//第三个是是否独占队列一般是false不独占,第四个是消费完成后是否自动删除 true代表删除,false代表不删除
//第五个参数是额外附加参数
channel.queueDeclare("hello",true,false,true,null);
//basicPublish 第一个参数代表交换机名称,第二是队列名称,第三个是额外的队列配置 第四个参数就是发送的消息
channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello,world".getBytes());
//关闭管道和连接
RabbitmqUtil.closeConnAndChannel(channel,connection);
}
}
消费者代码
Consumer.java
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 通过工具类获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// '参数1':用来声明通道对应的队列 hello
// '参数2':用来指定是否持久化队列 true
// '参数3':用来指定是否独占队列 false 一般都是不独站队列 让多个连接可以共同向一个队列生产消费消息
// '参数4':用来指定是否自动删除队列 false
// '参数5':对队列的额外配置 是一个Map类型
channel.queueDeclare("hello",true,false,true,null);
//参数一代表队列名,参数二是否开启自动确认机制,参数三,消费时的回调接口
channel.basicConsume("hello",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收的消息是:"+new String(body));
}
});
}
}
第二种模型(Work queue)
C1和C2都是消费者,P代表生产者,中间红色的部分是消息队列。
Work queue被称为任务队列。当消息处理比较耗时时,生产的速度大于消费的速度,长此以往,消息会在消息队列中越来愈多,无法及时处理。此时可以使用work模型,让多了消费者绑定到一个队列中,共同消费队列中的消息。
消息提供者
public class Provider {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
//建立通道
Channel channel = connection.createChannel();
//建立队列
channel.queueDeclare("work",false,false,false,null);
//生产消息
for (int i = 0; i < 10; i++) {
channel.basicPublish("","work",null,(i+":work queue").getBytes());
}
//关闭资源
RabbitmqUtil.closeConnAndChannel(channel,connection);
}
}
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException {
// 通过工具类获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
//每次只确认一条消息
channel.basicQos(1);
// '参数1':用来声明通道对应的队列 hello
// '参数2':用来指定是否持久化队列 true
// '参数3':用来指定是否独占队列 false 一般都是不独站队列 让多个连接可以共同向一个队列生产消费消息
// '参数4':用来指定是否自动删除队列 false
// '参数5':对队列的额外配置 是一个Map类型
channel.queueDeclare("work",false,false,false,null);
//参数一代表队列名,参数二是否开启自动确认机制,参数三,消费时的回调接口
channel.basicConsume("work",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者 1 接收的消息是:"+new String(body));
//进行手动确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException {
// 通过工具类获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
//一次只接受1条消息进行 确认
channel.basicQos(1);
// '参数1':用来声明通道对应的队列 hello
// '参数2':用来指定是否持久化队列 true
// '参数3':用来指定是否独占队列 false 一般都是不独站队列 让多个连接可以共同向一个队列生产消费消息
// '参数4':用来指定是否自动删除队列 false
// '参数5':对队列的额外配置 是一个Map类型
channel.queueDeclare("work",false,false,false,null);
//参数一代表队列名,
// 参数二是否开启自动确认机,
// 在开启自动确认消息机制时,RabbitMQ会认为只要消费者从队列中拿走消息就认为已经消费完成,就会将队列中的消息标记为已消费实际消费过程中有可能出现
//开启自动确认机制有时会造成消息丢失,如果一个消费者在执行过程中宕机了那他未完成的消息也会丢失,我们想让宕机后未消费的消息转移到正常运行的消费者上进行消费,
// 所以需要关闭自动确认机制(设置成false),进行手动确认
// 参数三,消费时的回调接口
channel.basicConsume("work",false,new DefaultConsumer(channel){
@lombok.SneakyThrows
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 消费者2消费的速度小于消费者1
Thread.sleep(2000);
System.out.println("消费者 2 接收的消息是:"+new String(body));
//进行手动确认消息 false代表不开启多次确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
第三种模型(Fanout)
扇出(fanout)又称广播
广播模式下,消息发送流程
- 可以有多个消费者
- 每个消费者都有自己的队列
- 每个队列都要绑定到Exchange(交换机)
- 生产者发送消息,只能发送给交换机,交换机决定要发送到哪个队列,生产者无法决定
- 交换机把消息发送给绑定过的所有队列
- 队列的消费者都能够拿到消息,实现一条消息被多个消费者消费
消息生产者
public class Provider {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitmqUtil.getConnection();
//获取管道
Channel channel = connection.createChannel();
//将管道绑定交换机
channel.exchangeDeclare("logs","fanout");
//发送消息内容
channel.basicPublish("logs","",null,"fanout 广播模式".getBytes());
//关闭连接
RabbitmqUtil.closeConnAndChannel(channel,connection);
}
}
消费者
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
//绑定交换机
channel.exchangeDeclare("logs","fanout");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queue,"logs","");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
System.out.println("消费者1: "+new String(body));
}
});
}
}
其中一个生产者多个消费者进行消费,广播的效果就是可以将同一条消息发送给所有消费者绑定的队列
第四种模式(Routing)
Routing订阅模式-Direct(直连)
在Fanout模式中,一条消息,会被所有订阅的队列都消费但是在某些场景下,我们希望不同的消息被不同的队列消费。这时候就要用到Direct类型的Exchange。
在Direct下:
1、队列与交换机的绑定不能是任意绑定,而是要指定一个RoutingKey(路由Key)
2、消息的发送方在向交换机发送消息时,需要指定RoutingKey。
3、Exchange不在吧消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的RoutingKey与消息的Routing Key完全一致才会接收到消息。
消息生产者
public class Provider {
public static void main(String[] args) throws IOException {
// 获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通信管道
Channel channel = connection.createChannel();
// 交换器名称
String exechange = "logs_direct";
channel.exchangeDeclare(exechange,"direct");
// 指定路由key
String router_key = "error";
// 向交换器发送消息
channel.basicPublish(exechange,router_key,
null,("这是基于direct模型发布的 route_key="+router_key+" 发送的消息").getBytes());
RabbitmqUtil.closeConnAndChannel(channel,connection);
}
}
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
String exechange = "logs_direct";
//创建交换机类型是direct
channel.exchangeDeclare(exechange,"direct");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机,指明路由id
channel.queueBind(queue,exechange,"info");
channel.queueBind(queue,exechange,"warning");
channel.queueBind(queue,exechange,"error");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1: "+new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
String exechange = "logs_direct";
//创建交换机类型是direct
channel.exchangeDeclare(exechange,"direct");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机,指明路由id
channel.queueBind(queue,exechange,"error");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2: "+new String(body));
}
});
}
}
第五种模型(Topic)
Topic模型的Exchange与Direct相比,是可以根据RoutingKey的不同把消息路由到不同的队列。只不过Topic类型的Exchange可以让队列在绑定路由key的时候使用通配符,这种模型的路由key一般都是由一个或多个单词组成,多个单词之间以“.”分割。
#通配符
*表示可以匹配一个单词
\# 表示可以匹配一个或多个单词
例如:user.\# 可以代表user.login或者user.logout ,user.register.check等
user.* 表示user.login;user.lgout但是不能表示user.register.check
生产者
public class Provider {
public static void main(String[] args) throws IOException {
// 获取连接对象
Connection connection = RabbitmqUtil.getConnection();
// 创建通信管道
Channel channel = connection.createChannel();
// 交换器名称
String exechange = "topics";
channel.exchangeDeclare(exechange,"topic");
// 指定路由key
String router_key = "response.error";
// 向交换器发送消息
channel.basicPublish(exechange,router_key,
null,("这是基于topic模型发布的 route_key="+router_key+" 发送的消息").getBytes());
RabbitmqUtil.closeConnAndChannel(channel,connection);
}
}
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
String exechange = "topics";
//创建交换机类型是direct
channel.exchangeDeclare(exechange,"topic");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机,指明路由id
channel.queueBind(queue,exechange,"response.*");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1: "+new String(body));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
String exechange = "topics";
//创建交换机类型是direct
channel.exchangeDeclare(exechange,"topic");
//创建临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机,指明路由id
channel.queueBind(queue,exechange,"response.#");
//消费消息
channel.basicConsume(queue,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1: "+new String(body));
}
});
}
}
三.RabbitMQ的应用场景
1.异步处理
场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式
- 串行方式: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西。
- 并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。
- 消息队列:假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回.
消息队列: 引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理
由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。
2.应用解耦
场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。
这种做法有一个缺点:当库存系统出现故障时,订单就会失败。订单系统和库存系统高耦合,引入消息队列。
- 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
- 库存系统:订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。
3.流量削峰
场景: 秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用:
-
可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
-
可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.。
2.秒杀业务根据消息队列中的请求信息,再做后续处理。
四.rabbitmq如何保证消息在投递的过程中不被丢失
首先,介绍下,图中模块表示含义:
Upstream Service表示生产端;
Downstream Service表示消费端;
MQ Broker可能是一个MQ集群;
Callback Service表示回调服务;
还是以订单为例。
第一步:先将订单业务入库,然后把消息发送到broker端的一个队列1。注意这次,我并不是再把我订单消息又存储到另外一个数据库中,这里只进行一次入库操作。
第二步:第一步发送消息后,设置一个延迟时间,比如五分钟再次发送该消息,这条消息会发送到broker端的队列2;
第三步:消费端去监听指定的队列1,对消息进行消费处理;
第四步:消费端把消息真正处理完之后,还要自己内部再生成一条新的消息叫做send confirm 确认。这条confirm的确认消息也会发送到broker端的一个队列3。
第五步:回调服务会有一个Confirm Listener去监听这个队列3,如果回调服务收到了来自消费端回送的这条confirm消息,那么,回调服务则认为消费端对数据消费成功,对这条消息做一个持久化的存储,即将消息存入MSG DB。
第六步:五分钟之后,延迟投递的消息到达broker端指定队列2。回调服务会去监听队列2,如果延迟消息到达队列2,那么回调服务就会去检查MSG DB数据库,查看这条消息是否已经被消费端消费。
若MSG DB中存在记录,则回调服务什么都不做。
若MSG DB中不存在记录,说明消费者一直没有返回响应数据或者在返回过程中由于网络原因导致返回失败,这时。回调服务会主动发起RPC通信,发送一套resend的命令(带上消息的id),告知生产者刚才发的这条消息未找到,重新重新发送。然后,生产端再重复执行第一步。
3、消息延迟投递要点
延迟投递:相比第一种消息落库方案,该方案生产端在发送消息到broker端队列1时,还额外延迟发送同一条消息到broker端另一个队列2。
回调检查:若消费端对队列1中数据消费成功,则回调服务监听到消费端的确认消费消息,会将这条消息持久化到MSG DB中。当回调服务监听到队列2的延迟消息时,检查MSG DB是否存在记录。有记录则不处理,无记录则回调服务需发送一条resend命令给生产端,执行第一步重复操作。
极端情况:如果消费端一直没有返回confirm确认消息,或者回调服务在更新MSG DB时出现异常,怎么办?延迟投递能提供补偿机制。因为五分钟之后,回调服务一定能监听到这条消息,然后去MSG DB查找该消息记录是否存在,从而决定生产端是否需要重新发送消息。
4、消息延迟投递优点
少做一次DB的存储,提高性能:
可能最开始,若执行两次insert持久化操作,可能最多每秒1000单,但如果只持久化一次,可能就是每秒2000单,这样就相当于节省一台服务器了,而且减少了这种两次持久化数据库操作可能出现的问题。
异步补偿机制,提供可靠性:两个DB实现解耦。主流核心链路:订单消息入库,生产端发送订单消息到broker端,消费者负责监听队列进行消费。Callback提供的是一个补偿服务,它不是业务高峰期的核心链路,而是将它拆出来作为一个单独的服务进行消息的异步补偿。
总结:消息延迟投递方案最大限度的节省了一次数据信息落库的操作,提高整个高并发性能。同时,回调服务提供的异步补偿机制,让生产端可靠性投递有了进一步保障。
五.如何解决消息重复消费的问题(幂等性的问题)
- 当拿到这个消息做数据库的insert操作。那就容易了,给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
- 当拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
- 如果上面两种情况还不行,准备一个第三方存储,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
六.如何解决消息堆积的问题
网上普通回答
- 修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停止
- 临时建立好原先10倍或者20倍的queue数量
- 然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
- 接着临时征用10倍机器来部署consumer,每一批consumer消费一个临时queue的数据
- 这种做法相当于临时将queue资源和consumer资源扩大了10倍,以正常的10倍速度
但是这种方法远水解决不了进渴
用Shovel
- 概念:当某个队列的消费堆积严重时,比如超过某个设定的阀值,就可以通过Shovel将队列消息移交到别的一个集群。
- 当检测到一个队列出现严重的消息堆积的时候,比如可以通过/api/queue/vhost/name接口获取到队列的消息个数超过2000w或者消息的占用大小超过10g的时候,就启用shovel1将队列queue中的消息转发到备份集群的队列queue2
- 当检测到队列queue的消息个数低于100w的时候或者大小小于1GB就停止shovel1,然后等queue慢慢处理剩余的堆积。
- 当queue的消息个数低于10W或者大小小于100MB时,就开启shovel2将队列queue2中暂存的消息返还给对queue。
- 当检测到队列queue消息个数大于100W或者大小高于1GB就将shovel2停掉。
七.什么是死信队列,造成死信队列的原因是什么? 如何处理死信消息?
先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,自然就有了死信队列;
RabbitMQ的死信队列
对rabbitmq来说,产生死信的来源大致有如下几种:
- 消息被拒绝(basic.reject或basic.nack)并且requeue=false.
- 消息TTL过期
- 队列达到最大长度(队列满了,无法再添加数据到mq中)
实现死信队列步骤:
- 首先需要设置死信队列的 exchange 和 queue,然后进行绑定.
Exchange:dlx.exchange
Queue:dlx.queue
RoutingKey:# 代表接收所有路由 key
- 然后我们进行正常声明交换机、队列、绑定,只不过我们需要在普通队列加上一个参数即可:
arguments.put(“x-dead-letter-exchange”,’ dlx.exchange’ ) - 这样消息在过期、requeue失败、 队列在达到最大长度时,消息就可以直接路由到死信队列!
生产者:
public class DeadProducer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
String exchangeName = "test_dlx_exchange";
String routingKey = "item.update";
String msg = "this is dlx msg";
//我们设置消息过期时间,10秒后再消费 让消息进入死信队列
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.expiration("10000")
.build();
channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
System.out.println("Send message : " + msg);
channel.close();
connection.close();
}
}
消费者:
public class DeadConsumer {
public static void main(String[] args) throws Exception {
//创建连接、创建channel忽略 内容可以在上面代码中获取
String exchangeName = "test_dlx_exchange";
String queueName = "test_dlx_queue";
String routingKey = "item.#";
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//必须设置参数到 arguments 中
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-dead-letter-exchange", "dlx.exchange");
channel.exchangeDeclare(exchangeName, "topic", true, false, null);
//将 arguments 放入队列的声明中
channel.queueDeclare(queueName, true, false, false, arguments);
//一般不用代码绑定,在管理界面手动绑定
//channel.queueBind(queueName, exchangeName, routingKey);注释这行代码主要是测试控制台是否打印队列消息。如果打印,说明打印的是死信队列里面的消息
//声明死信队列
channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
channel.queueDeclare("dlx.queue", true, false, false, null);
//路由键为 # 代表可以路由到所有消息
channel.queueBind("dlx.queue", "dlx.exchange", "#");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
//6. 设置 Channel 消费者绑定队列
channel.basicConsume(queueName, true, consumer);
}
}
八.如何实现消息限流?
提供者
public class QosProducer {
public static void main(String[] args) throws Exception {
//1. 创建一个 ConnectionFactory 并进行设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
//2. 通过连接工厂来创建连接
Connection connection = factory.newConnection();
//3. 通过 Connection 来创建 Channel
Channel channel = connection.createChannel();
//4. 声明
String exchangeName = "test_qos_exchange";
String routingKey = "item.add";
//5. 发送
String msg = "this is qos msg";
for (int i = 0; i < 10; i++) {
String tem = msg + " : " + i;
channel.basicPublish(exchangeName, routingKey, null, tem.getBytes());
System.out.println("Send message : " + tem);
}
//6. 关闭连接
channel.close();
connection.close();
}
}
消费者
public class QosConsumer {
public static void main(String[] args) throws Exception {
//1. 创建一个 ConnectionFactory 并进行设置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
factory.setAutomaticRecoveryEnabled(true);
factory.setNetworkRecoveryInterval(3000);
//2. 通过连接工厂来创建连接
Connection connection = factory.newConnection();
//3. 通过 Connection 来创建 Channel
final Channel channel = connection.createChannel();
//4. 声明
String exchangeName = "test_qos_exchange";
String queueName = "test_qos_queue";
String routingKey = "item.#";
channel.exchangeDeclare(exchangeName, "topic", true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.basicQos(0, 3, false);
//一般不用代码绑定,在管理界面手动绑定
channel.queueBind(queueName, exchangeName, routingKey);
//5. 创建消费者并接收消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body)
throws IOException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("[x] Received '" + message + "'");
channel.basicAck(envelope.getDeliveryTag(), true);
}
};
//6. 设置 Channel 消费者绑定队列
channel.basicConsume(queueName, false, consumer);
}
}