RabbitMQ的使用
1、MQ是什么
MQ:Message Queue :消息队列
队列:简单的是就是一种数据结构 先进先出
消息队列:就是用来进行消息传输的
消息中间件:就是用来传输消息的中间载体
编程中 有点类似咋们的 协议
中间件 他只有一个作用:就是将你的信息发送到接受方 他并不关心 你发送的数据长啥样? 就类似于咋们生成中一个快递员的职责
RabbitMQ就是消息中间件
2、使用这个MQ能干什么
2.1、流量消峰(解决高并发)
2.2、模块之间的异步通信
3、消息队列的中间件有哪些
ActiveMQ---------JMS(SUN公司提供的规范) Java message Server
RabbitMQ-------在当下很多公司都用这一个
RocketMQ------阿里的
kafka------------用的比较多—最初的设计 是用来 完成分布式下日志的收集框架
4、RabbitMQ的基本安装
#安装之前需要的环境
yum install epel-release
yum install erlang
#安装rabbitMQ了
下载rpm文件
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.15/rabbitmq-server-3.6.15-1.el7.noarch.rpm
#下载完成需要安装
yum install rabbitmq-server-3.6.15-1.el7.noarch.rpm
#设置开机启动
systemctl enable rabbitmq-server.service
#查看服务的状态
systemctl status rabbitmq-server.service
#启动这个服务
systemctl start rabbitmq-server.service
#停止这个服务
systemctl stop rabbitmq-server.service
#查看当前所有的用户
rabbitmqctl list_users
#查看guest用户所有拥有的权限
rabbitmqctl list_user_permissions guest
#删除原来的guest用户
rabbitmqctl delete_user guest
#添加一个新的用户
rabbitmqctl add_user zhangsan 12345678
#给小波波设置个角色(tag)
rabbitmqctl set_user_tags zhangsan administrator
#给xiaobobo赋予权限
rabbitmqctl set_permissions -p / zhangsan ".*" ".*" ".*"
#查看用户所拥有的权限
rabbitmqctl list_user_permissions zhangsan
#开启web的管理端
rabbitmq-plugins enable rabbitmq_management
5、RabbitMQ中的五种通信模型
5.1、helloworld模型
意思是:生产者将消息发送到队列 然后队列将这个消息发送给消费者
5.1.1、导包
<!--导入RabbitMQ的相关的包-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.5.0</version>
</dependency>
5.1.2、生产者的写法
//申明队列的名字
private static final String QUEUE_NAME="nz-helloword";
public static void main(String[] args) throws IOException, TimeoutException {
//第一步:获取连接
Connection connection = ConnectionUtils.getConnection();
//第二步:创建数据传输的通道
Channel channel = connection.createChannel();
//第三步:申明队列
/**
* 第一个参数:队列的名字
* 第二个参数:是否持久化 比如现在发送到队列里面的消息 如果没有持久化 重启这个队列后数据会丢失(false) true:重启之后数据依然在
* 第三个参数:是否排外
* 1:连接关闭之后 这个队列是否自动删除
* 2:是否允许其他通道来进行访问这个数据
* 第四个参数:是否允许自动删除
* 就是当最后一个连接断开的时候 这个时候是否允许自动删除这个队列
* 第五个参数:申明队列的时候 要附带的一些参数
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发送数据到队列
/**
* 第一个参数:exchange 交换机 没有就设置为"值就可以了"
* 第二个参数:原本的意思是路由的key 现在没有key直接使用队列的名字
* 第三个参数:发送数据到队列的时候 是否要带一些参数 没有带任何参数
* 第四个参数:向队列中发送的数据
*/
channel.basicPublish("",QUEUE_NAME,null,"我是1111".getBytes());
channel.close();
connection.close();
}
5.1.3、消费者的写法
//申明队列的名字
private static final String QUEUE_NAME="nz-helloword";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消费者的申明
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
*
* @param consumerTag:这个消息的唯一标记
* @param envelope:信封(请求的消息属性的一个封装)
* @param properties:前面队列带过来的值
* @param body :接受到的消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接受到的消息是:"+new String(body));
//进行手动应答
/**
* 第一个参数:自动应答的这个消息标记
* 第二个参数:false 就相当于告诉队列受到消息了
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//绑定这个消费者
/**
* 第一个参数:队列的名字
* 第二个参数:是否自动应答
* 第三个参数:消费者的申明
*/
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
5.2、work模型
多个消费者消费的数据之和才是原来队列中的所有数据 适用于流量的消峰
5.2.1、生产者的编写
public class Producer {
private static final String QUEUE_NAME="nz-work";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//下面向队列中发送100条消息
for (int i = 0; i <100 ; i++) {
channel.basicPublish("",QUEUE_NAME,null,("我是工作模型:"+i).getBytes());
}
channel.close();
connection.close();
}
}
5.2.2、消费者1的编写
public class Consumer {
//申明队列的名字
private static final String QUEUE_NAME="nz-work";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消费者的申明
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
*
* @param consumerTag:这个消息的唯一标记
* @param envelope:信封(请求的消息属性的一个封装)
* @param properties:前面队列带过来的值
* @param body :接受到的消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1接受到的消息是:"+new String(body));
//进行手动应答
/**
* 第一个参数:自动应答的这个消息标记
* 第二个参数:false 就相当于告诉队列受到消息了
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//绑定这个消费者
/**
* 第一个参数:队列的名字
* 第二个参数:是否自动应答
* 第三个参数:消费者的申明
*/
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
5.2.3、消费者2的编写
public class Consumer1 {
//申明队列的名字
private static final String QUEUE_NAME="nz-work";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消费者的申明
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
*
* @param consumerTag:这个消息的唯一标记
* @param envelope:信封(请求的消息属性的一个封装)
* @param properties:前面队列带过来的值
* @param body :接受到的消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2接受到的消息是:"+new String(body));
//进行手动应答
/**
* 第一个参数:自动应答的这个消息标记
* 第二个参数:false 就相当于告诉队列受到消息了
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//绑定这个消费者
/**
* 第一个参数:队列的名字
* 第二个参数:是否自动应答
* 第三个参数:消费者的申明
*/
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
5.3、发布订阅模型
就是队列里面的消息会被几个消费者 同时接受到
模型 适合于做模块之间的异步通信
例子: 就可以使用这种模型 来发送日志信息 ------ 立马就会被log收集程序
收集到 直接写到咋们的文件里面
例子:springcloud的config组件里面通知配置自动更新
例子:缓存同步也可以使用这一个
例子:高并发下实现下单逻辑
5.3.1、编写生产者
public class Producer {
//申明交换机的名字
private static final String EXCHANGE_NAME="nz-fanout-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明交换机
/**
* 第一个参数:交换机的名字
* 第二个参数:交换机的类型
* 交换机的类型是不能乱写的 如果使用的是发布订阅模型 只能写 fanout
*/
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//发送消息到交换机了
for (int i = 0; i <100 ; i++) {
channel.basicPublish(EXCHANGE_NAME,"",null,("发布订阅模型的值:"+i).getBytes());
}
//关闭资源
channel.close();
connection.close();
}
}
5.3.2、编写消费者
public class Consumer {
//申明交换机的名字
private static final String EXCHANGE_NAME="nz-fanout-01";
//队列的名字
private static final String QUEUE_NAME="nz-fanout-queue1";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//将队列绑定到交换机
/**
* 第一个参数:队列的名字
* 第二个参数:交换机的名字
* 第三个参数:路由的key(现在没有用到这个路由的key)
*/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//申明消费者
DefaultConsumer defaultConsumer = 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.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
5.3.2、编写消费者2
public class Consumer1 {
//申明交换机的名字
private static final String EXCHANGE_NAME="nz-fanout-01";
//队列的名字
private static final String QUEUE_NAME="nz-fanout-queue2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//将队列绑定到交换机
/**
* 第一个参数:队列的名字
* 第二个参数:交换机的名字
* 第三个参数:路由的key(现在没有用到这个路由的key)
*/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//申明消费者
DefaultConsumer defaultConsumer = 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));
}
};
//就进行消费者的绑定
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
5.4、路由模型
路由模式相当于是分布订阅模式的升级版(就是根据路由的key(routingkey)来判断是否路由到哪一个队列里面去)。
5.4.1、生产者的写法
public class Producer {
private static final String EXCHANGE_NAME="nz-exchange-direct-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
/**
* 如果玩的是路由模型 交换机的类型只能是 direct
*/
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//发送信息到交换机
for (int i = 0; i <100 ; i++) {
if(i%2==0){
//这个路由的key是可以随便设置的
channel.basicPublish(EXCHANGE_NAME,"xiaowangzi",null,("路由模型的值:"+i).getBytes());
}else{
//这个路由的key是可以随便设置的
channel.basicPublish(EXCHANGE_NAME,"xiaodidi",null,("路由模型的值:"+i).getBytes());
}
}
channel.close();
connection.close();
}
}
5.4.2、消费者1的写法
public class Cosnumer1 {
private static final String QUEUE_NAME="nz-direct-queue-01";
private static final String EXCHANGE_NAME="nz-exchange-direct-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//绑定队列到交换机
//第三个参数:表示的是路由key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"xiaodidi");
//申明消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//这里就是接受消息的地方
System.out.println("路由key是xiaodidi的这个队列接受到数据:"+new String(body));
}
};
//绑定消费者
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
5.4.3、消费者2的写法
public class Consumer2 {
private static final String QUEUE_NAME="nz-direct-queue-02";
private static final String EXCHANGE_NAME="nz-exchange-direct-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");
//绑定队列到交换机
//第三个参数:表示的是路由key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"xiaowangzi");
//申明消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//这里就是接受消息的地方
System.out.println("路由key是xiaowangzi的这个队列接受到数据:"+new String(body));
}
};
//绑定消费者
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
5.5、topic模式
说明:topic模式相当于是对 路由模式的一个升级 topic模式主要就是在匹配的规则上可以实现模糊匹配
5.5.1、生产者的写法
public class Producer {
private static final String EXCHANGE_NAME = "nz-exchange-topic-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
/**
* 如果玩的是路由模型 交换机的类型只能是 direct
*/
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//发送信息到交换机
for (int i = 0; i < 100; i++) {
//这个路由的key是可以随便设置的
//topic在路由基础上只有 路由的key发生改变 其余的都不变
channel.basicPublish(EXCHANGE_NAME, "xiaowangzi.xiaowangzi.xiaowangzi", null, ("路由模型的值:" + i).getBytes());
}
channel.close();
connection.close();
}
}
5.5.3、消费者1的写法
public class Cosnumer1 {
private static final String QUEUE_NAME="nz-topic-queue-01";
private static final String EXCHANGE_NAME="nz-exchange-topic-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//绑定队列到交换机
//第三个参数:表示的是路由key
/**
* 注意 * :只是代表一个单词
* # :这个才代表 一个或者多个单词
* 记住如果有多个单词组成的路由key 那么多个单词之间使用 . 好连接
*
*
*/
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"xiaodidi.*");
//申明消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//这里就是接受消息的地方
System.out.println("路由key是xiaobobo的这个队列接受到数据:"+new String(body));
}
};
//绑定消费者
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
5.5.3、消费者2的写法
public class Consumer2 {
private static final String QUEUE_NAME="nz-topic-queue-02";
private static final String EXCHANGE_NAME="nz-exchange-topic-01";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//绑定队列到交换机
//第三个参数:表示的是路由key
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"xiaowangzi.#");
//申明消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//这里就是接受消息的地方
System.out.println("路由key是xiaowangzi的这个队列接受到数据:"+new String(body));
}
};
//绑定消费者
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
备注:使用了交换机发送了数据 如果没有消费者的话那么这个数据会发生丢失 通过设置这样的属性来解决这个问题
channel.basicPublish(EXCHANGE_NAME, "xiaowangzi.xiaowangzi.xiaowangzi", MessageProperties.PERSISTENT_TEXT_PLAIN, ("路由模型的值:" + i).getBytes());
备注2:通道的问题
原本没有通道我们也可以完成这个请求 RabbitMQ官方考虑到一个问题生产者 和 消费者 实际上 Connection 引入这个通道这个概念 是为了降低TCP连接的这样一个消耗 相当于是为了 TCP的复用 还有一个目的 就是为了线程隐私 相当于每一个线程都给你创建了一个通道
6、RabbitMQ中的一些高级属性
6.0、参数的含义
channel.queueDeclare(QUEUE_NAME,true,false,true,null);
第二个参数:如果是false 重启之后 队列都没有了 数据也会丢失
第三个参数:连接一旦关闭 那么就会删除这个队列
第四个参数:就是最后一个消费者 退出去之后 那么这个队列是否自动删除
channel.basicPublish("",QUEUE_NAME,null,"我是1111".getBytes());
第一个参数 :交换机
第二个参数:路由key
第三个参数:设置的队列的属性
第四个参数:值
6.1、confirm机制
问题:就是放到队列中的消息 怎么保证一定就是成功的放入了队列
引入了 confirm机制:这个机制的意思是 只要放消息到 queue是成功的那么队列就一定会进行反馈
public class Producer {
//申明队列的名字
private static final String QUEUE_NAME="nz-confirm-01";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//第一步:开启confirm消息确认机制
channel.confirmSelect();
//我们就要对消息的可达性实施监听
//下面就是对消息的签收情况进行确认
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("发送成功的监听.....");
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("发送失败的监听.....");
}
});
channel.queueDeclare(QUEUE_NAME,false,false,true,null);
channel.basicPublish("",QUEUE_NAME,null,"我是1111".getBytes());
}
}
6.2、return机制
场景:我们在发送消息的是时候、我们指定的交换机不存在 或者 指定的路由key不存在 这种时候我们就需要监听这种不可达的消息 我们的return机制就产生了
前提:当前的队列必须要有消费者存在
//有一个参数需要设置
mandatory 如果设置为ture:就表示的是要监听不可达的消息 进行处理
如果设置为false 那么队列端会直接删除这个消息
6.2.1、生产者的编写
public class Producer {
private static final String EXCHANGE_NAME="test_return_exchange1";
//是能路由的key
private static final String ROUTING_KEY="return.save";
//是不能路由的key
private static final String ROUTING_ERROR_KEY="abc.save";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//添加监听
channel.addReturnListener(new ReturnListener() {
/**
*
* @param i:队列响应给浏览器的状态码
* @param s:表示的是状态码对应的文本信息
* @param s1:交换机的名字
* @param s2:表示的是路由的key
* @param basicProperties:表示的是消息的属性
* @param bytes:消息体的内容
* @throws IOException
*/
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("监听到不可达的消息");
System.out.println("状态码:"+i+"---文本信息:"+s+"---交换机名字:"+s1+"----路由的key:s2");
System.out.println("监听到不可达的消息");
System.out.println("监听到不可达的消息");
System.out.println("监听到不可达的消息");
}
});
channel.basicPublish(EXCHANGE_NAME,ROUTING_ERROR_KEY,true,null,"这里是测试Return机制".getBytes());
}
6.2.2、消费者的编写
public class Consumer {
private static final String EXCHANGE_NAME="test_return_exchange1";
//是能路由的key
private static final String ROUTING_KEY="return.#";
//制定绑定的队列
private static final String QUEUE_NAME="test_return_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//申明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");
//绑定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,ROUTING_KEY);
//申明消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到这个消息了....");
}
};
//进行消费的绑定
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
6.3、消费端的限流
场景:消费者死了 队列里面一瞬间就就积累了上万条数据、这个时候当我们打开客户端的时候、瞬间就有巨量的信息给推送过来、但是我们的客户端是没有办法同时处理这么多数据的 结果就是消费者死了…
这种场景下我们就需要对消费端进行限流
6.3.1、生产者的编写
public class Producer {
//申明队列的名字
private static final String QUEUE_NAME="nz1904-limit-01";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
for (int i = 0; i <100 ; i++) {
channel.basicPublish("",QUEUE_NAME, null,("我是张三"+i).getBytes());
}
channel.close();
connection.close();
}
}
6.3.2、消费者1的编写
public class Consumer {
//申明队列的名字
private static final String QUEUE_NAME="nz-limit-01";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//消费者的申明
DefaultConsumer defaultConsumer = 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);
}
};
//绑定这个消费者
/**
* 第一个参数:队列的名字
* 第二个参数:是否自动应答
* 第三个参数:消费者的申明
*/ channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
6.3.3、消费者2的编写
public class Consumer1 {
//申明队列的名字
private static final String QUEUE_NAME="nz-limit-01";
public static void main(String[] args) throws IOException, TimeoutException {
//获取连接
Connection connection = ConnectionUtils.getConnection();
//创建通道
final Channel channel = connection.createChannel();
//申明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//设置限流机制
/**
* 第一个参数:消息本身的大小 如果设置为0 那么表示对消息本身的大小不限制
* 第二个参数:告诉rabbitmq不要一次性给消费者推送大于N个消息 你要推送的前提是
* 现在这N个消息 已经手动被确认 已经完成
* 第三个参数:true/false :是否将上面的设置应用于整个通道 true :表示整个通道的消费者
* 都是这个策略 如果是false表示的是 只有当前的consumer 是这个策略
*/
channel.basicQos(0,5,false);
//结论:实际上如果不设置的话 分配任务的事 一开始就分配好了
//必须手动签收
//消费者的申明
DefaultConsumer defaultConsumer = 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));
try {
Thread.sleep(200);
}catch (Exception err){
}
//进行手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//绑定这个消费者
/**
* 第一个参数:队列的名字
* 第二个参数:是否自动应答
* 第三个参数:消费者的申明
*/
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
6.4、TTL队列(Time To Live)
场景:我要下单 下单之后 在一定的时间内、我的订单如果没有被处理 那么自动失效
备注:就是队列中的消息是有时间限制的、如果超时那么这个消息将会被队列给删除
public class Producer {
//申明队列的名字
private static final String QUEUE_NAME="nz-ttl-01";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
//我们只需要给下面的队列设置好属性 那么这个队列 就自动变成 ttl队列了
Map<String,Object> map=new HashMap<>();
map.put("x-message-ttl",5000);
channel.queueDeclare(QUEUE_NAME,false,false,false,map);
channel.basicPublish("",QUEUE_NAME, null,("我是张三").getBytes());
channel.close();
connection.close();
}
}
6.5、死信队列
什么是死信队列
当消息在队列中编程死信之后、可以定义它重新push 到另外一个交换机上、这个交换机 也有自己对应的队列 这个队列就称为死信队列
死信:
发送到队列中的消息被拒绝了
消息的ttl时间过期
队列达到了最大长度 再往里面放信息
在满足上面死信的前提下 、现在我们可以定义一个队列 这个队列专门用来
死信队列也是一个正常的交换机、和一般的交换机没有什么区别
当这个队列中如果有这个死信的时候、rabbitmq就会将这个消息自动发送到我们提前定义好的死信队列中去(简单的说就是路由到另外一个队列)
6.5.1、生产者的编写
public class Producer {
//定义的是队列(正常的交换机) 这里发送消息是在交换机上面
private static final String EXCHANGE_NAME="ttl-dlx-didi-exchange";
//定义一个路由的key
private static final String ROUTING_KEY="dlx.#";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
for (int i = 0; i <5 ; i++) {
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,false,null,("我是张三"+i).getBytes());
}
}
}
6.5.2、消费者的编写
public class Consumer {
//定义的是交换机
private static final String EXCHANGE_NAME="ttl-dlx-didi-exchange";
//正常情况下的队列
private static final String QUEUE_NAME="ttl-dlx-didi-queue";
//定义死信队列的交换机的名字
private static final String DLX_EXCHANGE_NAME="dlx-didi-exchange";
//死信队列的定义
private static final String DLX_QUEUE_NAME="dlx-didi-queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
//创建交换机和队列进行绑定
channel.exchangeDeclare(EXCHANGE_NAME,"topic",true);
//队列的申明
//我们要申明成死信队列
Map<String,Object> map=new HashMap<>();
map.put("x-message-ttl",5000);
//添加一个死信的属性 //后面这个名字就是死信队列交换机的名字
map.put("x-dead-letter-exchange",DLX_EXCHANGE_NAME);
channel.queueDeclare(QUEUE_NAME,true,false,false,map);
//进行队列和交换机进行绑定
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"dlx.#");
//上面是正常的队列的申明
//下面就是死信队列的申明
channel.exchangeDeclare(DLX_EXCHANGE_NAME,"topic");
//申明队列
channel.queueDeclare(DLX_QUEUE_NAME,true,false,false,null);
//绑定这个死信队列
channel.queueBind(DLX_QUEUE_NAME,DLX_EXCHANGE_NAME,"#");
//直接性的来调用这个
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("获取到数据了:"+new String(body));
}
};
//绑定消费者
channel.basicConsume(DLX_QUEUE_NAME,true,defaultConsumer);
//现在有这个问题呀?
}
}
6.6、消费者端手动签收和消息的重回队列
6.6.1、生产者的编写
public class Producer {
//申明队列的名字
private static final String QUEUE_NAME="nz-ack-02";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicPublish("",QUEUE_NAME, null,"我是1111".getBytes());
channel.close();
connection.close();
}
}
6.6.2、消费者的编写
public class Consumer {
//申明队列的名字
private static final String QUEUE_NAME="nz-ack-02";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接受到的消息是:"+new String(body));
/**第一个参数:当前消息的标记
* 第二个参数:是否批量进行应答
* 下面是签收
*/
//channel.basicAck(envelope.getDeliveryTag(),false);
//下面也可以拒绝签收
/**
* 第三个参数:表示决绝签收之后这个消息是否要重回队列?
*/
channel.basicNack(envelope.getDeliveryTag(),false,true);
}
}; channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}