Java整合RabbitMQ

本博客只作为个人记录或讲师上课使用,谨慎参考。
Rabbit MQ安装,请参考本人博客:RabbitMQ 安装教程


一、引入相关依赖

创建普通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交换机对应的队列,从而不去影响正常的队列处理数据

在这里插入图片描述
在这里插入图片描述

  1. 如上图可以看到 我们需要一个单独的 交换机 和 队列, 用于死信的存放,死信队列和交换机 跟普通的队列和交换机没有区别,只是涵义上这么称呼,创建死信队列和交换机代码如下:
        // 死信处理-创建 死信交换机和死信队列
        channel.exchangeDeclare("交换机-处理死信", "topic", true, false, null);
        channel.queueDeclare("队列-处理死信", true, false, false, null);
        channel.queueBind("队列-处理死信", "交换机-处理死信", "#"); // #号 代表匹配多个词  这里的一个# 则是匹配所有队列
  1. 那么在声明 队列的时候 我们需要手动绑定 死信交换机,如下:
 		// 创建交换机
 		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);
  1. 那么此时,我们就为队列绑定 死信队列的操作 完成了,接下来可以模式出现死信的三种情况

      - 消息被拒绝
      - 消息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();
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ark方舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值