本博客只作为个人记录或讲师上课使用,谨慎参考。
Rabbit MQ安装,请参考本人博客:RabbitMQ 安装教程
目录
- 一、引入相关依赖
- 二、 创建两个Java文件分别对应的是 生产者 和 消费者(简单应用)
- 三、生产者 Productor.java
- 四、生产者 Consumer.java
- 五、交换机的使用 Exchange(重要)
- 六、direct直连型交换机
- 七、fanout型交换机(废掉路由规则)
- 八、topic主题型交换机(主题匹配)
- 九、header型交换机
- 十、设置大小限制的队列
- 十一、确认消息已经发送到了MQ服务器
- 十二、生产端确认消息是否发送到了MQ(谨慎使用)
- 十三、Return 作用 ( 确保路由规则设置正确 )
- 十四、消费者限流QOS(削峰作用、重回队列)
- 十五、自定义消费者处理
- 十六、TTL(消息的最大生存时长)
- 十七、死信队列
一、引入相关依赖
创建普通JavaSE Maven项目 或者 直接创建springboot项目都可,本文不与Spring整合。
<!--rabbitmq 依赖-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
</dependency>
二、 创建两个Java文件分别对应的是 生产者 和 消费者(简单应用)
生产者 Productor.java
消费者 Consumer.java
三、生产者 Productor.java
1、Productor文件代码如下
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
//接收数据
// 参数 : 队列名称(从指定得队列中拿数据)
// 参数 : 是否自动确认
// 参数 : 接收数据得对象
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body);
System.out.println("获取到数据: " + str);
//获取BasicProperties信息
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String key : headers.keySet()) {
System.out.println(key + ": "+ headers.get(key));
}
}
}
};
// 创建接收数据得对象 , 需要传入 通道。
channel.basicConsume("小米手机货架", true, consumer);
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
2、解析Productor文件代码
RabbitMq服务的菜单作用:
那么在 编写代码的流程和菜单的排列顺序是一样的:
创建连接 ——> 创建通道 ——> 创建交换机(交换机的用法在本博客末尾处) ——> 创建队列
(1)、首先在 ‘创建连接工厂’ 进行 连接RabbitMQ
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
// 创建连接
Connection connection = connectionFactory.newConnection();
(2)、首先在 '创建双向通道' , 进行数据得收发
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
(3)、声明一个消息队列
/*
声明一个消息队列
参数一(queue): 消息队列名称
参数二(durable): 如果我们声明一个持久队列,则为true(该队列将在服务器重启后保留下来)
参数三(exclusive): 如果我们声明一个独占队列,则为true(仅限此连接)
参数四(autoDelete): 若为true,则在消费掉这个消息队列的消息后,RabbitMQ 会自动删除该队列
参数五(durable): 队列的其他属性(构造参数)先设置为null 博客末尾有介绍
*/
channel.queueDeclare("小米手机货架", true, false, false, null);
在IDEA中可以进入queueDeclare( ) 方法中查看源码注释(看不懂注释可以在IDEA中安装Translate 翻译插件)
(4)、声明一个消息队列
那么这时候 创建连接 ——> 创建通道 ——> 创建交换机 ——> 创建队列 (暂时忽略交换机)整个流程我们完成了,我们现在只需要发送数据 到 队列中即可,那么看发送数据代码如下:
/*
AMQP.BasicProperties对象是RabbitMQ中内部类对象,在发送数据的时候 我们经常需要携带一些基本信息,
就像寄快递一样我们需要 在快递单上标注寄件人姓名等等,在使用basicPublish方法发送数据的时候,可以将该对象携带。
那么在RabbitMQ中,也定义了 MessageProperties.PERSISTENT_BASIC 等等一些模板,
这些模板返回的也是 AMQP.BasicProperties对象,所以我们一般使用手动定义基本信息。
*/
Map<String, Object> headers = new HashMap(); //头信息数据
headers.put("姓名", "caocoa");
headers.put("手机", "1313821xx12");
headers.put("地址", "南京市秦淮区xxx街道");
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties()
.builder()
.contentEncoding("utf-8") // 编码
.expiration("1000000") // 过期时间 单位:毫秒
.deliveryMode(2) // 1.不写入磁盘 2.写到磁盘上
.headers(headers) // 添加自定义的头信息(重点)
.build();
/*
发布信息到RabbitMQ的队列中
参数一(exchange):发布到哪一个交换机
参数二(routingKey):生产者发送消息到交换机并指定一个路由key,
消费者队列绑定到交换机时要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息),
但是此处暂没有设置到交换机 所以,如果程序中不涉及交换机,该routingKey参数 就是 队列名称
参数三(props):基本信息 AMQP.BasicProperties对象,不需要可以设置为null
参数四(body):发布的信息,需要是一个byte[]数组
*/
channel.basicPublish("", "小米手机货架", basicProperties, str.getBytes());
(5)、关闭通道和连接
// 断开通道
channel.close();
// 关闭连接
connection.close();
运行程序后在RabbitMQ图形化系统中可以看到:
当前RabbitMQ连接数(连接的IP信息等等):
当前服务中的队列
队列中的信息等等
注意点: 创建队列的时候,除了指定队列名称外,还有是否持久化队列以及独占队列、自动删除队列这三个参数的使用需要多动手操作一下。
四、生产者 Consumer.java
1、Consumer文件代码如下
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
//接收数据
// 参数 : 队列名称(从指定得队列中拿数据)
// 参数 : 是否自动确认
// 参数 : 接收数据得对象
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body);
System.out.println("获取到数据: " + str);
//获取BasicProperties信息
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String key : headers.keySet()) {
System.out.println(key + ": "+ headers.get(key));
}
}
}
};
// 创建接收数据得对象 , 需要传入 通道。
channel.basicConsume("小米手机货架", true, consumer);
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
2、解析Consumer文件代码
创建连接 、 创建通道的操作 和 生产这代码是一样的;
(1)、生产者已经将数据发送到了 RabbitMQ服务中,那么需要去获取RabbitMQ中的数据
// 获取生产者消息 的基本信息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body);
System.out.println("获取到数据: " + str);
//获取BasicProperties中header的信息,发送者自定义的
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String key : headers.keySet()) {
System.out.println(key + ": "+ headers.get(key));
}
}
}
};
// 接收数据
// 参数 : 队列名称(从指定得队列中拿数据)
// 参数 : 是否自动确认
// 参数 : 接收数据得对象
channel.basicConsume("小米手机货架", true, consumer);
注意此处有一个坑:
运行消费者程序:一直处于挂在状态,当Rabbit队列产生数据的时候 消费者就会实时接收到数据,
(2)、断开通道和连接(有坑你要乱关闭)
// 断开通道
channel.close();
// 关闭连接
connection.close();
五、交换机的使用 Exchange(重要)
交换机的好处是可以更加灵活的配置把消息路由到队列中
交换机的几种类型:直连型交换机,fanout交换机,topic交换机,header交换机
(下图为直连型交换机的流程图)
消息发送者不再直接将消息推送到队列中,而是推送到交换机中,由交换机的Binding Key 来自动匹配规则推送到哪一个队列中。
无论是哪一种交换机都涉及到一个绑定key(主要指定路由规则的含义)。
创建一个交换机的案例:
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(direct为直连型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机直连形-direct" , "direct" , true ,false,null);
交换机绑定队列案例:
// 声明队列
channel.queueDeclare("队列-direct",true,false,false,null);
// 绑定交换机
// 参数一:队列名称
// 参数二:交换机名称
// 参数三:routingKey路由键
channel.queueBind("队列-direct","交换机直连形-direct" , "key");
六、direct直连型交换机
不处理路由键(不是正真的不处理,而是使用给定路由键进行 一致完全的匹配)。只需要将简单的队列绑定到交换机上。发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
**直连型交换机案例:**
生产者:
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 Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(direct为直连型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机直连形-direct" , "direct" , true ,false,null);
// 声明队列
channel.queueDeclare("队列-direct",true,false,false,null);
// 绑定交换机
// 参数一:队列名称
// 参数二:交换机名称
// 参数三:routingKey路由键
channel.queueBind("队列-direct","交换机直连形-direct" , "key");
// 发送数据到交换机
String str = "你好我是盐城人";
channel.basicPublish("交换机直连形-direct" , "key" ,null , str.getBytes());
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
分析代码:
上述代码的流程是: 创建交换机(指定交换机的名称类型等)——> 声明队列——> 将交换机和队列进行绑定(需要指定routingKey路由键)——> 发送数据到交换机。
运行程序后在RabbitMQ图形化管理服务中 可以看到如下效果:
程序中创建的交换机
队列中显示了刚刚创建的消息队列:
记住: 程序是将数据发送给了交换机,由交换机指定发送给哪个指定的队列。
发送数据到交换机的时候要指定routingKey路由键,然后交换机和队列进行绑定的时候要自定routingKey路由键, 这时候发送数据到交换机的的时候,交换机会拿到生产者发送过来的数据和routingKey ,交换机根据routingKey去将数据转发到对应的队列中(在之前已经将队列和交换机进行了绑定 ,绑定的时候已经定义了 routingKey)
消费者:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String string = new String(body);
System.out.println(string);
// 获取基本信息
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String tmp : headers.keySet()) {
System.out.println(headers.get(tmp));
}
}
}
};
// 从队列中获取数据
channel.basicConsume("队列-direct", true, defaultConsumer);
// 不要关闭通道和连接 (坑)
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
七、fanout型交换机(废掉路由规则)
fanout型交换机和直连型交换机不同(直连型使用路由直接匹配队列),但是在fanout型交换机不处理路由键,只需将队列绑定到交换机上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout型交换机转发消息是最快的。
直连型交换机案例:
生产者:
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 Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(fanout为fanout交换机) , 注意fanout型交换机不需要routingKey匹配规则, 但是routingKey参数不能是null 或者""
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-fanout" , "fanout" , true ,false,null);
// 声明队列
channel.queueDeclare("队列-fanout",true,false,false,null);
// 绑定交换机
// 参数一:队列名称
// 参数二:交换机名称
// 参数三:routingKey路由键(fanout 弃用routingKey 写不写都一样, 但是不写的话 会报错 Invalid configuration: 'routingKey' must be non-null. 提示必须是非NUll类型 所以不能是null 或者 "")
channel.queueBind("队列-fanout","交换机-fanout" , "key");
// 发送数据到交换机
String str = "你好我是盐城人";
channel.basicPublish("交换机-fanout" , "fanout弃用了routingKey, 参数不能为空" ,null , str.getBytes());
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
注意:fanout型交换机不需要routingKey匹配规则, 但是程序中绑定交换机和队列 ,以及发送数据到交换机的时候 ,
routingKey参数 不能是 null 或者 “”。
八、topic主题型交换机(主题匹配)
将路由键和模式惊醒匹配,此时队列需要绑定在一个模式上。
. 单词的分隔符(不是必须,也是使用其他分隔符)
* 可以匹配一个单词,也可以是0个单词
# 可以匹配多个单词,或者是0个
当一个队列被绑定为routingKey为 # 时,他将会接收所有的消息,此时等价于fanout类型交换机。当routingKey 不包含 * 和 # 时,等价于direct型交换机。
下面这句话很重要,理解了基本就明白可以了:
生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要制定路由key(key匹配就能接受消息,key不匹配就不能接受消息)
下图为topic主题型交换机案例的 原理图:
topic主题型交换机案例(配合上图理解代码):
生产者:
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 Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(topic为主题型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-topic" , "topic" , true ,false,null);
// 声明队列
channel.queueDeclare("队列topic11",true,false,false,null);
channel.queueDeclare("队列topic22",true,false,false,null);
channel.queueDeclare("队列topic33",true,false,false,null);
// 绑定交换机
// 参数一:队列名称
channel.queueBind("队列topic11","交换机-topic" , "item.*.user");
channel.queueBind("队列topic22","交换机-topic" , "item.*.user");
channel.queueBind("队列topic33","交换机-topic" , "item.*.new");
// 发送数据到交换机 下面的代码交换机会将数据转发到 队列topic11 和 队列topic22 中 ,因为 只有 这两个队列的routingKey 符合 item.del.user
String str = "用户添加";
channel.basicPublish("交换机-topic" , "item.del.user" ,null , str.getBytes());
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
上述代码运行后,在RabbitMQ中可以看到 :
消费者(下面代码消费掉了 队列topic22 中的数据):
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String string = new String(body);
System.out.println(string);
// 获取基本信息
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String tmp : headers.keySet()) {
System.out.println(headers.get(tmp));
}
}
}
};
// 从队列中获取数据
channel.basicConsume("队列topic22", true, defaultConsumer);
// 不要关闭通道和连接 (坑)
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
九、header型交换机
不使用路由键。而是根据发送的消息内容中headers属性进行匹配。在绑定Queue与Exchange是定制一组键值对;当消息发送到RabbitMQ 时,会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否者不会路由到该队列。
匹配规则 x-match 有下列两种类型:
x-match = all :表示所有的键值对都匹配才能接受到消息(不包括x-match)
x-match = any:表示至少有一个简直对匹配就能接受到消息
创建一个headers交换机:
// 声明headers交换机
// 参数一:交换机名称
// 参数二:交换机类型(header型交换机,该类型的交换机,也是废弃了routingKey 匹配队列的方式)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-headers" , "headers" , true ,false,null);
那么 交换机和队列进行绑定的时候,需要传入一个headers( Map类型)的参数,通过map里面的参数进行 和 推送数据时传入的参数 AMQP.BasicProperties对象里面的header属性(Map类型) 数据进行数据的匹配,(请认真对待这句话)代码案例如下:
声明一个队列:
// 声明队列
channel.queueDeclare("队列headers",true,false,false,null);
绑定 队列 和 交换机 ,并且制定匹配规则**( 该案例是设置x-match 的参数为 all )**:
// 设置headers交换机 需要的路由规则
Map<String , Object> headers = new HashMap<>();
headers.put("x-match","all"); // x-match 设置为all的话 匹配规则是 username和password 都必须匹配上去才可以,也就是把消息路由到消息队列的规则
headers.put("username" , "admin");
headers.put("password" , "123");
// 绑定交换机
// 参数一:队列名称
// 参数二: 交换机名称
// 参数三: 路由名称
// 参数四: headers 需要进行的匹配参数
channel.queueBind("队列headers","交换机-headers" , "" , headers);
上诉代码中 channel.queueBind 队列和交换机进行绑定时,路由可以为空,参数四是 headers(就是用户在推送数据到交换机的时候传入的 自定义的AMQP.BasicProperties对象参数)需要匹配的规则。
既然匹配规则是针对 推送数据时传入的 自定义的AMQP.BasicProperties对象里面的headers参数,那么我们在推送数据到RabbitMQ的时候就需要传入该参数,如下:
推送数据代码:
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder()
.contentEncoding("UTF-8")
.expiration("1000000")
.deliveryMode(2)
.headers(AMQPHeaders)
.build();
// 发送数据到交换机 参数三:如何是header类型的交换机,该参数的里面的headers属性就必须要填写了
String str = "信息内容....";
channel.basicPublish("交换机-headers" , "" , properties , str.getBytes());
生产者(完整案例代码):
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.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(header型交换机,该类型的交换机,也是废弃了routingKey 匹配队列的方式)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-headers" , "headers" , true ,false,null);
// 声明队列
channel.queueDeclare("队列headers",true,false,false,null);
// 设置headers交换机 需要的路由规则
Map<String , Object> headers = new HashMap<>();
headers.put("x-match","all"); // x-match 设置为all的话 匹配规则是 username和password 都必须匹配上去才可以,也就是把消息路由到消息队列的规则
headers.put("username" , "admin");
headers.put("password" , "123");
// 绑定交换机
// 参数一:队列名称
// 参数二: 交换机名称
// 参数三: 路由名称
// 参数四: headers 需要进行的匹配参数
channel.queueBind("队列headers","交换机-headers" , "" , headers);
// 设置特殊信息的参数
Map<String , Object> AMQPHeaders = new HashMap<>();
AMQPHeaders.put("username" , "admin");
AMQPHeaders.put("password" , "123");
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder()
.contentEncoding("UTF-8")
.expiration("1000000")
.deliveryMode(2)
.headers(AMQPHeaders)
.build();
// 发送数据到交换机
String str = "信息内容....";
channel.basicPublish("交换机-headers" , "" , properties , str.getBytes());
// 断开通道
channel.close();
// 关闭连接
connection.close();
}
}
在 交换机 和 队列 进行绑定的时候,headers匹配规则中,
x-match 的取值为all 的话,那么推送数据的时候的 AMQP.BasicProperties参数的headers自定义信息中的数据和同时包含username:admin 和 password:123 一摸一样,否则数据推送不到绑定的队列中。
那么上述代码是设置 x-match 的参数为 all 的匹配规则,还有一个 any取值,使用是一样的,只是匹配规则不同而已。
十、设置大小限制的队列
在创建队列的时候,我们需要指定 队列最大可以存放多少条数据
Map<String , Object> props = new HashMap<>();
props.put("x-max-length",2);
/*
声明队列
参数四:Map集合 指定队列的其他属性(构造参数) 在map中添加 x-max-length:2 属性即可限制队列大小为两条数据
*/
channel.queueDeclare("队列-direct",true,false,false,props);
十一、确认消息已经发送到了MQ服务器
必须在消息发送前设置确认模式
// 打开生产者的确认模式
channel.confirmSelect();
// 添加确认监听
channel.addConfirmListener();
十二、生产端确认消息是否发送到了MQ(谨慎使用)
生产者将信道(创建的通道)设置成了confirm 模式,一旦消息被MQ接收了,MQ就会发送一个确认给生产者(包括消息的唯一ID);否者就表示消息没有到达MQ,可能由于网络闪断的原因。
在创建信道后,需要进行如下操作:
// 打开生产者的确认模式
channel.confirmSelect();
// 添加确认监听 注意此处到达MQ不代表到达了队列,所以该监听器用的不多,至于交换机有没有正确的转发到队列上,那么这里是无法监听的
channel.addConfirmListener(new ConfirmCallback() {
@Override
public void handle(long deliveryTag, boolean multiple) throws IOException {
// 成功发送到MQ的处理
}
}, (deliveryTag, multiple) -> {
// 发送失败的处理,这种情况很难被测试到(除非网络闪断的情况,所以测试自行把握)
});
需要手动 开启生产者的确认模式 , 添加确认监听。
完整代码案例如下:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmCallback;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(topic为主题型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-topic", "topic", true, false, null);
// 声明队列
channel.queueDeclare("队列topic11-确认发送到达了队列", true, false, false, null);
// 打开生产者的确认模式
channel.confirmSelect();
// 添加确认监听 注意此处到达MQ不代表到达了队列,所以该监听器用的不多,至于交换机有没有正确的转发到队列上,那么这里是无法监听的
channel.addConfirmListener((deliveryTag, multiple) -> {
System.out.println("消息成功到达MQ");
}, (deliveryTag, multiple) -> {
System.out.println("消息到达MQ失败!");
});
/*
// 添加确认监听 和 上面的写法一样只不过使用的 jdk1.8 lambda表达式
// 打开生产者的确认模式
channel.confirmSelect();
// 添加确认监听 注意此处到达MQ不代表到达了队列,所以该监听器用的不多,至于交换机有没有正确的转发到队列上,那么这里是无法监听的
channel.addConfirmListener(new ConfirmCallback() {
@Override
public void handle(long deliveryTag, boolean multiple) throws IOException {
System.out.println("消息成功到达MQ");
}
}, (deliveryTag, multiple) -> {
System.out.println("消息到达MQ失败!");
});
*/
// 绑定交换机
channel.queueBind("队列topic11-确认发送到达了队列", "交换机-topic", "item.*.user");
// 发送数据到交换机 下面的代码交换机会将数据转发到 队列topic11 和 队列topic22 中 ,因为 只有 这两个队列的routingKey 符合 item.del.user
String str = "删除添加";
channel.basicPublish("交换机-topic", "item.del.user", null, str.getBytes());
// 由于需要监听回调,所以不可以关闭连接和信道
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
注意,需要监听回调,所以不要关闭信道和连接。
十三、Return 作用 ( 确保路由规则设置正确 )
在某种情况下,如果我们在发送消息的时候,当前的路由key错误,需要监听这种不可达到的消息,就需要使用return listener。 用于处理交换机 路由错误 和 rountingKey路由错误的处理。
basicPulish( )方法的参数 Mandatory为false时,如果消息无法正确路由到队列后,会被MQ直接丢失(程序自动处理错误消息,也就是忽略)。
basicPulish 的参数 Mandatory为true时,消息无法正确路由到队列后,会返回给发送者(手动处理错误消息)
basicPulish( )方法有很多的重载方法, 其中有一个方法是可以传人Mandatory参数的,该参数是boolean类型的数据
设置手动处理错误消息:
设置Mandatory参数为true,开启手动处理错误信息
// basicPublish方法有很多的重载方法,其中 有一个参数 mandatory(第三个参数):true是需要手动处理错误消息,不是true的话无法手动处理错误的返回
channel.basicPublish("交换机-topic-returnListener", "item.del.user111", true,null, str.getBytes());
在发布消息前添加returnListener监听:
//在发布消息前添加returnListener监听
channel.addReturnListener(new ReturnListener() {
//replyCode 状态码 , replyText 返回的文本消息 , exchange 哪个交换机出现的问题 , routingKey 你设置的路由键 , properties参数 , body[] 你传过去的数据
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("replyCode :" + replyCode);
System.out.println("replyText :" + replyText);
System.out.println("exchange :" + exchange);
System.out.println("routingKey :" + routingKey);
System.out.println("body :" + new String(body));
}
});
生产者代码案例:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(topic为主题型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-topic-returnListener", "topic", true, false, null);
// 声明队列
channel.queueDeclare("队列topic11-确认发送到达了队列-return", true, false, false, null);
// 绑定交换机
channel.queueBind("队列topic11-确认发送到达了队列-return", "交换机-topic-returnListener", "item.*.user");
String str = "删除添加";
// basicPublish,其中 有一个参数 mandatory:true是需要手动处理错误消息,不是true的话无法手动处理错误的返回
channel.basicPublish("交换机-topic-returnListener", "item.del.user111", true,null, str.getBytes());
//在发布消息前添加returnListener监听
channel.addReturnListener(new ReturnListener() {
//replyCode 状态码 , replyText 返回的文本消息 , exchange 哪个交换机出现的问题 , routingKey 你设置的路由键 , properties参数 , body[] 你传过去的数据
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("replyCode :" + replyCode);
System.out.println("replyText :" + replyText);
System.out.println("exchange :" + exchange);
System.out.println("routingKey :" + routingKey);
System.out.println("body :" + new String(body));
}
});
// 由于需要监听回调,所以不可以关闭连接和信道
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
注意: 只要消息没有正确到队列就会发生错误,此时我们就可以接收到错误消息,上面的案例中消息的routingKey没有任何队列可以匹配到,所以没法路由到队列中去。
十四、消费者限流QOS(削峰作用、重回队列)
消费者限流主要在消费端进行设置。
简单说限流的作用就是: 设置消息从消息中间件到达消费者的过程不要太急了,消费者积累了很多的信息,要不要一下子都甩给消费者,如果都甩到消费者也有可能会造成消息的崩溃。
消费者确认一条在处理一条,消费者端需要设置三个东西。
1、设置传递的最大消息数
channel.basicQos(0, 1, false);
// 三个参数 int prefetchSize, int prefetchCount, boolean global 据说 prefetchSize 和 global这两项Rabbi i他MQ没有实现,暂且不说。
// 那么 prefetchCount 描述 传递的最大消息数 ; 设为1 就是 每次 RabbitMQ 每次发送一条数据 到消费者,消费者 确认后 ,再次发送未处理的数据过。
2、设置好basicQos( ) 方法后,还要取消自动确认 autoAck 设置fasle
// 从队列中获取数据 ,设置限流后需要取消自动确认 autoAck 设置fasle
channel.basicConsume("队列-QOS-限流", false, defaultConsumer);
参数三:DefaultConsumer 类中 重写 handleDelivery() 方法需要加上 回馈信息,告诉RabbitMQ 服务器 ,如果没有加上 Rabbit MQ 会一直等待 信息响应(不然不会发送下一条数据),如果信道中断RabbitMQ会启动缓存机制,将刚刚发送给消费者的信息,进行回滚重回队列。
3.回馈信息给RabbitMQ确认接收
// 如果设置了限流,每次处理数据需要告诉RabbitMQ队列 , 否则服务关闭后 RabbitMQ 会数据回滚,同时不会获取下一批数据
// envelope.getDeliveryTag()可以获取到唯一的标识 , false 就是一条一条处理回馈
channel.basicAck(envelope.getDeliveryTag() , false );
消费端完整代码案例:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 限流
channel.basicQos(0, 1, false);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
public Integer i = 0 ;
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String string = new String(body);
System.out.println(string);
// 获取基本信息
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String tmp : headers.keySet()) {
System.out.println(headers.get(tmp));
}
}
System.out.println(i++);
// 如果 设置了 限流 每次处理数据,需要 告诉 Rabbit MQ队列 , 否则服务关闭后 RabbitMQ 会数据回滚,同时不会获取下一批数据
// envelope.getDeliveryTag()可以获取到唯一的标识 , false 就是一条一条处理回馈
channel.basicAck(envelope.getDeliveryTag() , false );
}
};
// 从队列中获取数据 ,设置限流后需要取消自动确认 autoAck 设置fasle
channel.basicConsume("队列-QOS-限流", false, defaultConsumer);
// 不要关闭通道和连接 (坑)
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
上述代码通过 断电的方式可以观察到,如下图:
上图,断点还没有进行响应确认,那么下图Rabbit MQ服务中可以看到如下信息:
十五、自定义消费者处理
继承 DefaultConsumer即可。
十六、TTL(消息的最大生存时长)
1. TTL 是Time To Live 的缩写,也就是生存时间 2. RabbitMQ 支持消息的过期时间,在消息发送时可以进行指定 3. RabbitMQ 支持队列的过期时间,从消息入列开始计算,只要超过了队列的超时时间配置,那么消息会自动清除
有两种情况设置的方式:
第一种 设置队列里面 所有消息 最长的身存时间(下面是设置了8秒):
Map<String ,Object> argss = new HashMap<String , Object>();
argss.put("x-message-ttl" , 8000); //消息6秒后还没有被消费,会被丢弃。
channel.**queueDeclare**(queueName , durable , exclusive , aytoDelete , argss);
第二种 设置发送的这一条消息的生存时间,时间超时后,消息自动被中间件删除
AMQP.Bac
发送10条信息(在发送消息的时候设置):
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("8000");
AMQP.BasicProperties properties = builder.build();
for (int i = 1; i < 11; i++) {
String str = "删除用户编号为: " + i + " 的用户。";
// basicPublish,其中 有一个参数 mandatory:true是需要手动处理错误消息,不是true的话无法手动处理错误的返回
channel.basicPublish("交换机-QOS-ttl", "item.del.user", true, properties, str.getBytes());
}
如果程序中 设置了消息 第一种和第二种 都存在, 那么还是优先使用 第一种全局的消息过期时间。
RabbitMQ 如何对某一个消息 设置TTL时间,那么这边有一个大坑要注意,如下:
坑 ——> RabiitMQ中 假设队列中有10条消息,其中有一条消息是设置了TTL时间,那么队列中 设置了TTL 过期时间的这个消息在队列中是不生效了, 除非当前队列中的所有消息都设置了 8秒的过期时间,否者就是不生效🙂, 所以在RabbitMq中的TTL实现上并不是很理想。
生产者代码:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(topic为主题型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-QOS-ttl2", "topic", true, false, null);
// 声明队列
channel.queueDeclare("队列-QOS-ttl2", true, false, false, null);
// 绑定交换机
channel.queueBind("队列-QOS-ttl2", "交换机-QOS-ttl2", "item.*.user");
// 设置消息过期时间
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("8000");
AMQP.BasicProperties properties = builder.build();
for (int i = 1; i < 11; i++) {
String str = "删除用户编号为: " + i + " 的用户。";
// basicPublish,其中 有一个参数 mandatory:true是需要手动处理错误消息,不是true的话无法手动处理错误的返回
channel.basicPublish("交换机-QOS-ttl2", "item.del.user", true, properties, str.getBytes());
}
//在发布消息前添加returnListener监听
channel.addReturnListener(new ReturnListener() {
//replyCode 状态码 , replyText 返回的文本消息 , exchange 哪个交换机出现的问题 , routingKey 你设置的路由键 , properties参数 , body[] 你传过去的数据
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("replyCode :" + replyCode);
System.out.println("replyText :" + replyText);
System.out.println("exchange :" + exchange);
System.out.println("routingKey :" + routingKey);
System.out.println("body :" + new String(body));
}
});
// 由于需要监听回调,所以不可以关闭连接和信道
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
十七、死信队列
某特殊场景,如让消息过期后,消息会被转移到死信队列上。
出现死信的三种情况
- 消息被拒绝 (在消费者端 使用 channel.basicReject(envelope.getDeliveryTag() , false); // 拒收 进行响应RabbitMQ描述拒收消息)
- 消息TTL过期
- 队列达到最大长度
给一个正常的队列A添加一个死信队列DLX,当A中出现死信时,会被路由到DLX交换机对应的队列,从而不去影响正常的队列处理数据
- 如上图可以看到 我们需要一个单独的 交换机 和 队列, 用于死信的存放,死信队列和交换机 跟普通的队列和交换机没有区别,只是涵义上这么称呼,创建死信队列和交换机代码如下:
// 死信处理-创建 死信交换机和死信队列
channel.exchangeDeclare("交换机-处理死信", "topic", true, false, null);
channel.queueDeclare("队列-处理死信", true, false, false, null);
channel.queueBind("队列-处理死信", "交换机-处理死信", "#"); // #号 代表匹配多个词 这里的一个# 则是匹配所有队列
- 那么在声明 队列的时候 我们需要手动绑定 死信交换机,如下:
// 创建交换机
channel.exchangeDeclare("交换机-deadletter", "topic", true, false, null);
Map<String , Object> props = new HashMap<>();
props.put("x-dead-letter-exchange" , "交换机-处理死信"); // 死信交换机名称
props.put("x-max-length",2);
// 声明队列
channel.queueDeclare("队列-deadletter", true, false, false, props);
-
那么此时,我们就为队列绑定 死信队列的操作 完成了,接下来可以模式出现死信的三种情况
- 消息被拒绝 - 消息TTL过期 - 队列达到最大长度
模拟消息拒收——> 我们需要在 消费者端的 自定义消费者处理中重写的 handleDelivery()方法 末尾 需要响应RabbitMQ服务器 :
拒收使用下方第二行代码:
channel.basicAck(envelope.getDeliveryTag() , false ); // 响应 RabbitMQ 接收成功
channel.basicReject(envelope.getDeliveryTag() , false); // 拒收
消费者端 记得要设置 自动确认机制 (ACK机制)为 false,取消自动确认。
那么此时,由于消费者拒收,原本的队列中的数据会转移到 死信交换机分发到死信队列上。可以在 死信队列上进行查看。
那么 我们 可以编写对应 死信队列的消费者端 ,死信队列的消费者端 和 普通的消费端 无区别。
消息TTL过期——> 我们需要在 生产者端,设置队列信息过期时间(设置过期时间,参考第十六章节设置过期时间的两种方法)
队列达到最大长度——> 我们需要在 生产者端,设置队列 消息的 最大容量 (设置队列的大小限制,参考第十章节)
简单案例如下:
消费者端:
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Productor {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
//======================================================================
// 死信处理-创建 死信交换机和死信队列
channel.exchangeDeclare("交换机-处理死信", "topic", true, false, null);
channel.queueDeclare("队列-处理死信", true, false, false, null);
channel.queueBind("队列-处理死信", "交换机-处理死信", "#"); // #号 代表匹配多个词 这里的一个# 则是匹配所有队列
//======================================================================
// 声明交换机
// 参数一:交换机名称
// 参数二:交换机类型(topic为主题型交换机)
// 参数三:如果我们声明持久交换,则为true(该交换将在服务器重新启动后继续存在)
// 参数四:消费完是否删除交换机
// 参数五:交换的其他属性(构造参数)
channel.exchangeDeclare("交换机-deadletter", "topic", true, false, null);
Map<String , Object> props = new HashMap<>();
props.put("x-dead-letter-exchange" , "交换机-处理死信"); // 死信交换机名称
props.put("x-max-length",2); // 设置队列大小限制
// 声明队列
channel.queueDeclare("队列-deadletter", true, false, false, props);
// 绑定交换机
channel.queueBind("队列-deadletter", "交换机-deadletter", "item.*");
// 设置消息过期时间 40秒
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("40000");
AMQP.BasicProperties properties = builder.build();
for (int i = 11; i < 21; i++) {
String str = "商品订单 " + i + ",请接收。";
// basicPublish,其中 有一个参数 mandatory:true是需要手动处理错误消息,不是true的话无法手动处理错误的返回
channel.basicPublish("交换机-deadletter", "item.order", true, properties, str.getBytes());
}
//在发布消息前添加returnListener监听
channel.addReturnListener(new ReturnListener() {
//replyCode 状态码 , replyText 返回的文本消息 , exchange 哪个交换机出现的问题 , routingKey 你设置的路由键 , properties参数 , body[] 你传过去的数据
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("===========数据推送到队列发送错误==========");
System.out.println("replyCode :" + replyCode);
System.out.println("replyText :" + replyText);
System.out.println("exchange :" + exchange);
System.out.println("routingKey :" + routingKey);
System.out.println("body :" + new String(body));
}
});
// 由于需要监听回调,所以不可以关闭连接和信道
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}
消费者(拒收的案列):
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("121.36.146.10");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
// 创建连接
Connection connection = connectionFactory.newConnection();
// 定义双向通道 , 进行数据得收发
Channel channel = connection.createChannel();
// 限流
channel.basicQos(0, 1, false);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String string = new String(body);
System.out.println(string);
Map<String, Object> headers = properties.getHeaders();
if (headers != null) {
for (String tmp : headers.keySet()) {
System.out.println(headers.get(tmp));
}
}
// 如果 设置了 限流 每次处理数据,需要 告诉 Rabbit MQ队列 , 否则服务关闭后 RabbitMQ 会数据回滚,同时不会获取下一批数据
// envelope.getDeliveryTag()可以获取到唯一的标识 , false 就是一条一条处理回馈
// channel.basicAck(envelope.getDeliveryTag() , false ); // 响应 RabbitMQ 接收成功
channel.basicReject(envelope.getDeliveryTag() , false); // 拒收 , 重点
}
};
// 从队列中获取数据 ,设置限流后需要取消自动确认 autoAck 设置fasle
channel.basicConsume("队列-deadletter", false, defaultConsumer);
// 不要关闭通道和连接 (坑)
// 断开通道
// channel.close();
// 关闭连接
// connection.close();
}
}