RabbitMQ

目录

初级

一.MQ的基本概念

二.RabbitMQ的安装和配置

三.RabbitMQ快速入门

四.RabbitMQ的工作模式

1.简单模式(上文已实现)

2.工作队列模式

3.Pub/Sub模式

4.路由模式

5.通配符模式(最强大的模式)

6.远程操纵模式

五.Spring整合RabbitMQ

六.SpringBoot整合RabbitMQ

高级

RabbitMQ高级特性

一.确认模式和回退模式

二.Consumer ACK

三.TTL

四.死信队列

五.延迟队列


初级

一.MQ的基本概念

二.RabbitMQ的安装和配置

三.RabbitMQ快速入门

1.入门程序

简单模式----RabbitMQ的六大工作模式之一。

需求:使用简单模式完成消息传递

①创建工程(生成者、消费者)

项目架构:

②分别添加依赖

生产者依赖:

<dependencies>
        <!--rabbitMq的java客户端-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.6.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

消费者依赖:

消费者依赖与消费者相同,复制生成者依赖即可。

③编写生产者发送消息

package cn.zcj.producer;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 发消息的生产者
 */
public class producer01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工场
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("127.0.0.1");//ip,默认为localhost
        factory.setPort(5672);//端口,默认为5672
        factory.setVirtualHost("/zcj");//虚拟机,默认为/
        factory.setUsername("ZCJZCJ");//用户名,默认为guest
        factory.setPassword("ZCJZCJ");//密码,默认为guest
        //3.创建连接Connection
        Connection connection = factory.newConnection();
        //4.创建Channel
        Channel channel = connection.createChannel();
        //5.创建队列Queue
        /*
        * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        * 参数解释:
        * 1.queue:队列名称
        * 2.durable:是否持久化,当rabbitMq重启后还在
        * 3.exclusive:
        *    *是否独占:只能有一个消费者监听这个队列
        *    *当Connection关闭时,是否删除这个队列
        * 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
        * 5.arguments:参数信息,暂时设置为null
        * */
        //如果没有一个名字叫helloWord的队列,则会创建该队列,有就不创建
        channel.queueDeclare("helloWord",true,false,false,null);
        String body = "hello,rabbitMq";
        //6.发送消息
        /*
        * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
        * 参数解释:
        * 1.exchange:交换机名称。简单模式不需要使用交换机,交换机名默认使用""
        * 2.routingKey:路由名称
        * 3.props:配置信息
        * 4.body:发送消息数据
        * */
        channel.basicPublish("","helloWord",null,body.getBytes());
        //7.释放资源
        channel.close();
        connection.close();
    }
}

【Bug总结】代码中,生产者方的队列名与路由名不同,不会报错,但是生产者会拿不到消息。

④编写消费者接收消

【注意】消费者端不必关流释放资源

package cn.zcj.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

public class consumer01 {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("127.0.0.1");//ip  默认值 localhost
        factory.setPort(5672); //端口  默认值 5672
        factory.setVirtualHost("/zcj");//虚拟机 默认值/
        factory.setUsername("ZCJZCJ");//用户名 默认 guest
        factory.setPassword("ZCJZCJ");//密码 默认值 guest
        //3. 创建连接 Connection
        Connection connection = null;
        //4. 创建Channel
        Channel channel = null;
        try {
            connection= factory.newConnection();
            channel = connection.createChannel();
            // 接收消息
            Consumer consumer = new DefaultConsumer(channel){
                /*
                    回调方法,当收到消息后,会自动执行该方法
                    1. consumerTag:标识
                    2. envelope:获取一些信息,交换机,路由key...
                    3. properties:配置信息
                    4. body:数据
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                                           AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("consumerTag:"+consumerTag);
                    System.out.println("Exchange:"+envelope.getExchange());
                    System.out.println("RoutingKey:"+envelope.getRoutingKey());
                    System.out.println("properties:"+properties);
                    System.out.println("body:"+new String(body));
                }
            };
        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1. queue:队列名称
            2. autoAck:是否自动确认,后面再讲
            3. callback:回调对象
         */
            channel.basicConsume("helloWord",true,consumer);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

四.RabbitMQ的工作模式

1.简单模式(上文已实现)

2.工作队列模式

依赖和简单模式导入的依赖相同(复制上文依赖即可)

2.1.生产者:

生产者(1个)

代码:

package cn.zcj.producer;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 发消息的生产者
 */
public class producerWorkQueue {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工场
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("127.0.0.1");//ip,默认为localhost
        factory.setPort(5672);//端口,默认为5672
        factory.setVirtualHost("/zcj");//虚拟机,默认为/
        factory.setUsername("ZCJZCJ");//用户名,默认为guest
        factory.setPassword("ZCJZCJ");//密码,默认为guest
        //3.创建连接Connection
        Connection connection = factory.newConnection();
        //4.创建Channel
        Channel channel = connection.createChannel();
        //5.创建队列Queue
        //如果没有一个名字叫helloWord的队列,则会创建该队列,有就不创建
        channel.queueDeclare("WorkQueue",true,false,false,null);
        for (int i = 0 ;i<10;i++){
            String body = "WorkQueue模式消息"+i;
            //6.发送消息
            channel.basicPublish("","WorkQueue",null,body.getBytes());
        }
        //7.释放资源
        channel.close();
        connection.close();
    }
}

2.2.消费者

消费者(2个)

消费者1代码:

package cn.zcj.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

public class consumerWorkQueue01 {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("127.0.0.1");//ip  默认值 localhost
        factory.setPort(5672); //端口  默认值 5672
        factory.setVirtualHost("/zcj");//虚拟机 默认值/
        factory.setUsername("ZCJZCJ");//用户名 默认 guest
        factory.setPassword("ZCJZCJ");//密码 默认值 guest
        //3. 创建连接 Connection
        Connection connection = null;
        //4. 创建Channel
        Channel channel = null;

        try {
            connection= factory.newConnection();
            channel = connection.createChannel();
            channel.queueDeclare("WorkQueue",true,false,false,null);
            // 接收消息
            Consumer consumer = new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                                           AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("body:"+new String(body));
                }
            };
            channel.basicConsume("WorkQueue",true,consumer);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

消费者2代码:将消费者1的代码复制一份后改下类名即可

【注意】启动时先启动两个消费者,再去启动生产者

测试结果:

从测试结果不难看出:消费者不会重复消费同一条消息,并且默认是两个消费者轮流(轮询)消费消息 。

总结:

3.Pub/Sub模式

模式说明:

依赖和简单模式导入的依赖相同(复制上文依赖即可)

生产者(1个):

package cn.zcj.producer;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 发消息的生产者
 */
public class producerPubSub {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工场
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("127.0.0.1");//ip,默认为localhost
        factory.setPort(5672);//端口,默认为5672
        factory.setVirtualHost("/zcj");//虚拟机,默认为/
        factory.setUsername("ZCJZCJ");//用户名,默认为guest
        factory.setPassword("ZCJZCJ");//密码,默认为guest
        //3.创建连接Connection
        Connection connection = factory.newConnection();
        //4.创建Channel
        Channel channel = connection.createChannel();
        //5.创建交换机
        /*
        * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
        * boolean internal, Map<String, Object> arguments)
        * 参数解析:
        * 1.exchange:交换机名称
        * 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
        *   DIRECT("direct"):定向
            FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
            TOPIC("topic"):统配符的方式
            HEADERS("headers"):参数匹配,使用较少,不做讲解
        * 3.durable:是否持久化
        * 4.autoDelete:自动删除
        * 5.internal:内部使用,一般false
        * 6.arguments:参数,暂时为null
        * */
        String exchangeName = "test_fanout";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
        //6.创建2个队列Queue
        String queueName01 = "test_fanout_queue01";
        channel.queueDeclare(queueName01,true,false,false,null);
        String queueName02 = "test_fanout_queue02";
        channel.queueDeclare(queueName02,true,false,false,null);
        //7.绑定队列和交换机(非常重要)
        /*
        * queueBind(String queue, String exchange, String routingKey)
        * 参数解释:
        * 1.queue:队列名称
        * 2.exchange:交换机名称
        * 3.routingKey:路由键,绑定规则
        *   如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
        *
        * */
        channel.queueBind(queueName01,exchangeName,"");
        channel.queueBind(queueName02,exchangeName,"");
        //8.发送消息
        String message = "日志信息:张三查询了数据库......";
        channel.basicPublish(exchangeName,"",null,message.getBytes());
        //9.释放资源
        channel.close();
        connection.close();
    }
}

【补充】交换机会将消息发送给每一个与之绑定的Queue容器。

消费者(2个)

消费者1

package cn.zcj.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class consumerPubSub01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("127.0.0.1");//ip  默认值 localhost
        factory.setPort(5672); //端口  默认值 5672
        factory.setVirtualHost("/zcj");//虚拟机 默认值/
        factory.setUsername("ZCJZCJ");//用户名 默认 guest
        factory.setPassword("ZCJZCJ");//密码 默认值 guest
        //3. 创建连接 Connection
        Connection connection = factory.newConnection();
        //4. 创建Channel
        Channel channel = connection.createChannel();
        String queueName01 = "test_fanout_queue01";
        String queueName02 = "test_fanout_queue02";
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:" + new String(body));
                System.out.println("收到消息了,将数据打印到控制台...");
            }
        };
        channel.basicConsume(queueName01, true, consumer);
    }
}

消费者2

复制消费者1的代码,然后在此处将名字改为queueName02

测试结果:

总结:

4.路由模式

模式介绍:

依赖和简单模式导入的依赖相同(复制上文依赖即可)

生产者代码实现

package cn.zcj.producer;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 发消息的生产者
 */
public class producerRouting {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工场
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("127.0.0.1");//ip,默认为localhost
        factory.setPort(5672);//端口,默认为5672
        factory.setVirtualHost("/zcj");//虚拟机,默认为/
        factory.setUsername("ZCJZCJ");//用户名,默认为guest
        factory.setPassword("ZCJZCJ");//密码,默认为guest
        //3.创建连接Connection
        Connection connection = factory.newConnection();
        //4.创建Channel
        Channel channel = connection.createChannel();
        //5.创建交换机
        /*
        * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
        * boolean internal, Map<String, Object> arguments)
        * 参数解析:
        * 1.exchange:交换机名称
        * 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
        *   DIRECT("direct"):定向
            FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
            TOPIC("topic"):统配符的方式
            HEADERS("headers"):参数匹配,使用较少,不做讲解
        * 3.durable:是否持久化
        * 4.autoDelete:自动删除
        * 5.internal:内部使用,一般false
        * 6.arguments:参数,暂时为null
        * */
        String exchangeName = "test_direct";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
        //6.创建2个队列Queue
        String queueName01 = "test_direct_queue01";
        channel.queueDeclare(queueName01,true,false,false,null);
        String queueName02 = "test_direct_queue02";
        channel.queueDeclare(queueName02,true,false,false,null);
        //7.绑定队列和交换机(非常重要)
        /*
         * queueBind(String queue, String exchange, String routingKey)
         * 参数解释:
         * 1.queue:队列名称
         * 2.exchange:交换机名称
         * 3.routingKey:路由键,绑定规则
         *   如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
         *
         * */
        //队列1绑定1次,绑定error
        channel.queueBind(queueName01,exchangeName,"error");
        //队列2绑定3次,绑定info、error、warning
        channel.queueBind(queueName02,exchangeName,"info");
        channel.queueBind(queueName02,exchangeName,"error");
        channel.queueBind(queueName02,exchangeName,"warning");
        //8.发送消息
        String message = "日志信息:张三查询了数据库......";
        channel.basicPublish(exchangeName,"error",
                null,message.getBytes());
        //9.释放资源
        channel.close();
        connection.close();
    }
}

生产者测试效果:

从测试结果不难看出,交换机只会为队列1分配类型为error的消息,但是会为队列2分配了类型为error、info、warning类型的消息。

【Bug总结】代码中交换机名字写错,后台编写代码的控制台不会报错,但是消息却无法成功发出。

消费者代码实现(2个)

消费者1

package cn.zcj.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class consumerRouting01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("127.0.0.1");//ip  默认值 localhost
        factory.setPort(5672); //端口  默认值 5672
        factory.setVirtualHost("/zcj");//虚拟机 默认值/
        factory.setUsername("ZCJZCJ");//用户名 默认 guest
        factory.setPassword("ZCJZCJ");//密码 默认值 guest
        //3. 创建连接 Connection
        Connection connection = factory.newConnection();
        //4. 创建Channel
        Channel channel = connection.createChannel();
        String directName01 = "test_direct_queue01";
        String directName02 = "test_direct_queue02";
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:" + new String(body));
                System.out.println("收到消息了,将数据打印到控制台...");
            }
        };
        channel.basicConsume(directName01, true, consumer);
    }
}

消费者2

复制消费者1的代码,改下名字即可

channel.basicConsume(directName02, true, consumer);//只改这行和类名即可

消费者代码测试 :

5.通配符模式(最强大的模式)

模式介绍:

依赖和简单模式导入的依赖相同(复制上文依赖即可)

生产者代码:

【注意】"*"代表一个单词,"#"代表0至多个单词

package cn.zcj.producer;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 发消息的生产者
 */
public class producerTopics {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工场
        ConnectionFactory factory = new ConnectionFactory();
        //2.设置参数
        factory.setHost("127.0.0.1");//ip,默认为localhost
        factory.setPort(5672);//端口,默认为5672
        factory.setVirtualHost("/zcj");//虚拟机,默认为/
        factory.setUsername("ZCJZCJ");//用户名,默认为guest
        factory.setPassword("ZCJZCJ");//密码,默认为guest
        //3.创建连接Connection
        Connection connection = factory.newConnection();
        //4.创建Channel
        Channel channel = connection.createChannel();
        //5.创建交换机
        /*
        * exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
        * boolean internal, Map<String, Object> arguments)
        * 参数解析:
        * 1.exchange:交换机名称
        * 2.type:交换机类型----很重要,如果类型不同,那么分发消息的规则也就不同
        *   DIRECT("direct"):定向
            FANOUT("fanout"):扇形(广播),发送消息到每一个与之绑定的队列
            TOPIC("topic"):统配符的方式
            HEADERS("headers"):参数匹配,使用较少,不做讲解
        * 3.durable:是否持久化
        * 4.autoDelete:自动删除
        * 5.internal:内部使用,一般false
        * 6.arguments:参数,暂时为null
        * */
        String exchangeName = "test_topics";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
        //6.创建2个队列Queue
        String queueName01 = "test_topics_queue01";
        channel.queueDeclare(queueName01,true,false,false,null);
        String queueName02 = "test_topics_queue02";
        channel.queueDeclare(queueName02,true,false,false,null);
        //7.绑定队列和交换机(非常重要)
        /*
        * queueBind(String queue, String exchange, String routingKey)
        * 参数解释:
        * 1.queue:队列名称
        * 2.exchange:交换机名称
        * 3.routingKey:路由键,绑定规则
        *   如果交换机的类型为fanout,routingKey的值为"",交换机会将消息发送给每一个与之绑定的Queue容器
        *
        * */
        //routing key----系统的名称.日志的级别
        //需求:所有error级别的日志存入数据库,所有order系统的日志存入数据库
        channel.queueBind(queueName01,exchangeName,"#.error");
        channel.queueBind(queueName01,exchangeName,"order.*");
        //需求:任何级别任何系统的消息都打印在控制台
        channel.queueBind(queueName02,exchangeName,"*.*");
        //8.发送消息
        String message = "日志信息:张三查询了数据库......";
        channel.basicPublish(exchangeName,"order.info",null,message.getBytes());
        //9.释放资源
        channel.close();
        connection.close();
    }
}

生产者测试:

消费者:

代码实现:

package cn.zcj.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class consumerTopics01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("127.0.0.1");//ip  默认值 localhost
        factory.setPort(5672); //端口  默认值 5672
        factory.setVirtualHost("/zcj");//虚拟机 默认值/
        factory.setUsername("ZCJZCJ");//用户名 默认 guest
        factory.setPassword("ZCJZCJ");//密码 默认值 guest
        //3. 创建连接 Connection
        Connection connection = factory.newConnection();
        //4. 创建Channel
        Channel channel = connection.createChannel();
        String topicsName01 = "test_topics_queue01";
        String topicsName02 = "test_topics_queue02";
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("body:" + new String(body));
                System.out.println("收到消息了,将数据存储到数据库...");
            }
        };
        channel.basicConsume(topicsName01, true, consumer);
    }
}

消费者2

//复制消费者1的代码,并只改这一行
channel.basicConsume(topicsName02, true, consumer);

测试:

总结:

6.远程操纵模式

略。

五.Spring整合RabbitMQ

Spring整合rabbitMQ生产者

1.依赖导入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>rabbitMq</artifactId>
        <groupId>cn.zcj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-rabbitMq-Proudcer</artifactId>

    <dependencies>
        <!--spring上下文-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
        <!--spring整合amqp的插件包-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <!---->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>
    </dependencies>
    <!--编译插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.resource下两个配置文件

第一个:rabbitmq.properties

rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj

第二个:spring-rabbitmq-producer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
    默认交换机类型为direct,名字为:"",路由键为队列的名称
    -->
    <!--
    参数解析:
    id:bean的名字
    name:queue的名字,可以与id不同
    auto-declare:是否自动创建
    auto-delete:自动删除,最后一个消费者和该队列断开连接后,自动删除该队列
    exclusive:受否独占
    durable:是否持久化
    -->
    <rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>

    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>

    <!--定义广播类型为交换机;并绑定上述两个队列-->
    <rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="spring_fanout_queue_1"/>
            <rabbit:binding queue="spring_fanout_queue_2"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

    <rabbit:direct-exchange name="aa">
        <rabbit:bindings>
            <!--direct类型的交换机绑定队列,key:路由key,queue:队列名称-->
            <rabbit:binding queue="spring_queue" key="XXX"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>

    <rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="zcj.*" queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="zcj.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="fengche.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

项目结构:

生产者测试:

package cn.zcj;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class Proudcer {
    //1.注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testHelloWorld(){
        //2.发送消息
        rabbitTemplate.convertAndSend("spring_queue","hello,spring-rabbitMQ");
    }

    @Test
    public void testFanout(){
        //2.发送消息
        rabbitTemplate.convertAndSend("spring_fanout_exchange","","rabbitMQ-Fanout");
    }

    @Test
    public void testTopics(){
        //2.发送消息
        rabbitTemplate.convertAndSend("spring_topic_exchange","zcj.test.MQ","rabbitMQ-Topics");
    }
}

测试结果:

整合消费者

消费者端的依赖和生产者相同,复制即可

2.resource下两个配置文件

第一个:rabbitmq.properties

rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj

第二个:spring-rabbitmq-consumer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <bean id="springQueueListener" class="cn.zcj.rabbitmq.listener.SpringQueueListener"/>
    <!--<bean id="fanoutListener1" class="cn.zcj.rabbitmq.listener.FanoutListener1"/>
    <bean id="topicListenerWell2" class="cn.zcj.rabbitmq.listener.TopicListenerWell2"/>-->

    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
        <rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
       <!-- <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
        <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
    </rabbit:listener-container>
</beans>

5

package cn.zcj.rabbitmq.listener;

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class SpringQueueListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        System.out.println(new String(message.getBody()));
    }

}

5

package cn.zcj;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {

    @Test
    public void testHelloWorld(){
        boolean flag = true;
        while (true){
        }
    }
}

5

六.SpringBoot整合RabbitMQ

1.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.zcj</groupId>
    <artifactId>SpringBootAndRabbitMQ</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <!--SpringBoot父工程依赖-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <dependencies>
        <!--SpringBoot整合RabbitMq的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--SpringBoot单元测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
</project>

2

spring:
  rabbitmq:
    host: 127.0.0.1
    username: ZCJZCJ
    password: ZCJZCJ
    virtual-host: /zcj
    port: 5672

2.启动类

package cn.zcj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitMqApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitMqApplication.class);
    }
}

2

package cn.zcj.rabbitMqConfig;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    public static final String EXCHANGE_NAME = "BootTopicExchange";
    public static final String QUEUE_NAME = "BootQueue";

    //1.交换机
    @Bean("bootExchange")
    public Exchange bootExchange(){
       return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    //2.队列
    @Bean("bootQueue")
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();
    }
    //3.让队列和交换机进行绑定
    /*
    * 1.知道哪个队列
    * 2.知道哪个交换机
    * 3.设置Routing Key
    * */
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }
}

2

package cn.zcj;

import cn.zcj.rabbitMqConfig.RabbitMQConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage(){
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.zcj","helloBoot");
    }
}

2测试结果

 项目结构:

消费者 :

1.依赖

<parent>
    <!--SpringBoot父工程依赖-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>

<dependencies>
    <!--SpringBoot整合RabbitMq的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!--SpringBoot单元测试依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

2

spring:
  rabbitmq:
    host: 127.0.0.1
    username: ZCJZCJ
    password: ZCJZCJ
    virtual-host: /zcj
    port: 5672

2监听器

package cn.zcj.MqListener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitMqListener {
    //队列名必须要正确对应
    @RabbitListener(queues = "BootQueue")
    public void ListenerQueue(Message message){
        System.out.println(new String(message.getBody()));
    }
}

2启动类

package cn.zcj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitMqConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitMqConsumerApplication.class);
    }
}

2开启启动类后,控制台的输出结果:

2项目结构

高级

一.RabbitMQ高级特性

1.确认模式和回退模式

消息可靠性投递:

【补充】确认模式回退模式都是生产者到Broker的可靠性保障,其中确认模式发生再Producer到exchange之间,回退模式发生在exchange到Queue容器之间。

代码实现

<dependencies>
    <!--spring上下文-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.7.RELEASE</version>
    </dependency>
    <!--spring整合amqp的插件包-->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    <!---->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.7.RELEASE</version>
    </dependency>
</dependencies>
<!--编译插件-->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

1

rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj

1

核心配置:

 完整配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <!--publisher-confirms==>开启确认模式-->
    <!--publisher-returns==>开启回退模式-->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>


    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--消息可靠性投递(生产端)-->
    <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"></rabbit:queue>
    <rabbit:direct-exchange name="test_exchange_confirm">
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>
</beans>

1

 package cn.zcj;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class Proudcer {
    //1.注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /*
    * 确认模式:
    *   步骤:
    *     1.确认模式开启:在配置文件里ConnectionFactory中开启publisher-confirms="true"
    *     2.在rabbitTemplate定义ConfirmCallback回调函数
    * */
    @Test
    public void testConfig(){
        //2.定义回调函数
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData:相关配置信息
             * @param ack:exchange交换机,是否收到了消息。成功为true,失败为false
             * @param cause:失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法执行了");
                if (ack){
                    System.out.println("收到消息成功");
                }else {
                    System.out.println("收到消息失败"+cause);
                }
            }
        });
        //3.发送消息
        //手动制造失败场景,故意写错交换机名字
        rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","hello");
    }
}

1

成功测试:

失败测试:

2.回退模式

/**
 * 回退模式:当消息发送给Exchange后,Exchange路由到Queue失败时,才会执行ReturnCallback
 * 步骤:
 *  1.开启回退模式:publisher-returns="true"
 *  2.设置ReturnCallback
 *  3.设置Exchange处理消息的模式(即此时路由消息已经失败了)
 *     1.如果消息没有路由到Queue,默认情况下直接丢弃这个消息
 *     2.如果消息没有路由到Queue,将消息返回给发送方ReturnCallback,这种方式要手动设置
 */
@Test
public void testReturn(){
    //1.设置交换机处理失败消息的模式,下面代码的意思是将发送失败的消息返回给生产者
    rabbitTemplate.setMandatory(true);

    //2.设置ReturnCallback
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
        /**
         * 只有当消息从交换机发送到Queue容器过程中失败时,下面这个回调函数才会执行
         * @param message:消息对象
         * @param replyCode:错误码
         * @param replyText:错误信息
         * @param exchange:交换机名
         * @param routingKey:路由键
         */
        @Override
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            System.out.println(message);
            System.out.println(replyCode);
            System.out.println(replyText);
            System.out.println(exchange);
            System.out.println(routingKey);
        }
    });
    //3.发送消息
    rabbitTemplate.convertAndSend("test_exchange_confirm","confirm222","HELLO");
}

1失败测试

总结:

2.Consumer ACK

介绍

消费端确认收到消息的三种模式------自动模式、手动模式、根据异常情况确认模式。

【补充】上文确认模式和回退模式均是生产者端的操作,是生产者到RabbitMQ Broker的可靠消息保障;而Consumer Ack均是消费者端的操作,是RabbitMQ Broker到消费者端的可靠消息保障。

环境准备

<dependencies>
    <!--spring上下文-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.7.RELEASE</version>
    </dependency>
    <!--spring整合amqp的插件包-->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    <!---->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.7.RELEASE</version>
    </dependency>
</dependencies>
<!--编译插件-->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

5

rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj

5

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <context:component-scan base-package="cn.zcj.rabbitmq.listener"></context:component-scan>
    <!--定义监听器容器-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
        <rabbit:listener ref="" queue-names="test_queue_confirm"/>
    </rabbit:listener-container>
</beans>

5

自动签收模式:

自动模式监听器

package cn.zcj.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.stereotype.Component;

@Component
public class AckListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        System.out.println(new String(message.getBody()));
    }

}

5将监听器类的类名首字母小写后写在配置文件里面

5测试

package cn.zcj;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {

    @Test
    public void testHelloWorld(){
        while (true){
        }
    }
}

5测试结果

5

手动签收模式:

1.设置手动签收。acknowledge="manual"

2.让监听器实现ChannelAwareMessageListener接口

3.如果消息处理成功,则调用channel的basicAck()签收

4.如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新给消费者consumer发消息

手动模式监听器:

package cn.zcj.rabbitmq.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class AckListenerManual implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //收到的当前消息的标签
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //1.接收转换消息
            System.out.println(new String(message.getBody()));
            //2.处理业务
            System.out.println("处理业务逻辑");
            //手动制造异常
            //int i = 3/0;
            //3.手动签收,true表示允许多条消息同时签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            //4.拒绝签收
            /*
            第三个参数:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
            */
            channel.basicNack(deliveryTag,true,true);
            //这个方法和上面的方法效果一样,但是不能一次签收多条消息
            //channel.basicReject(deliveryTag,true);
        }
    }
}

5将监听器类的类名首字母小写后写在配置文件里面

5测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {

    @Test
    public void testHelloWorld(){
        while (true){
        }
    }
}

5正常测试与异常测试:

根据异常情况确认模式:略。

消费端限流

介绍

2

Consumer限流机制:
 1.确保消费者消息确认机制为手动确认机制
 2.listener-container配置属性
   prefetch="10",表示消费者每次去MQ拉取10条消息来消费,直到10条消息都确认消费完毕后,再去拉取下一批消息(10条)

1.先改动配置(2处)

2.准备监听器

package cn.zcj.rabbitmq.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

/**
 * Consumer限流机制:
 * 1.确保消费者消息确认机制为手动确认机制
 * 2.listener-container配置属性
 *   prefetch="10",表示消费者每次去MQ拉取10条消息来消费,直到10条消息都确认消费完毕后,再去拉取下一批消息(10条)
 */
@Component
public class QosListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //1.获取消息
        System.out.println(new String(message.getBody()));
        //2.处理业务:略
        //3.消息手动签收,true表示允许多条消息同时签收;如果不签收,那么就无法拉取下一条消息消费
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,true);
    }
}

5测试:

@Test
public void testHelloWorld(){
    while (true){
    }
}

5测试结果:

总结:

3.TTL

TTL----是生产者方的操作

介绍:

1.队列统一过期

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <!--publisher-confirms==>开启确认模式-->
    <!--publisher-returns==>开启回退模式-->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>


    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--ttl-->
    <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
        <!--设置queue的参数-->
        <rabbit:queue-arguments>
            <!--x-message-ttl指队列的过期时间10秒后过期-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <rabbit:topic-exchange name="test_exchange_ttl">
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

2核心配置

2测试

@Test
public void testTtl(){
    for (int i = 1;i<=20;i++){
        rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.zcj","ttl"+i);
    }
}

测试结果(10秒后,20条消息全部过期):

2.消息单独过期

@Test
public void testTtl(){
    //消息处理对象,设置一些消息的参数信息
    MessagePostProcessor p = new MessagePostProcessor(){
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            //1.设置message的信息,5秒后过期
            message.getMessageProperties().setExpiration("5000");
            //2.返回该消息
            return message;
        }
    };
    //消息单独过期
    rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.zcj","ttl",p);

}

 测试:

【注意1】如果设置了消息过期时间,又设置了队列过期时间,以过期时间短的为准。

【注意2】队列消息过期后,会将队列这中的消息全部移除。

【注意3】如果某一个队列中有10条消息,其中只有1条消息单独设置了消息过期,即便消息已经过期了,队列中显示的依旧是存在10条消息,而不是9条,只有当消费者消费那条已经过期的消息时(此时消息会到达队列顶端),队列才会取判断那条消息是否过期,过期了就从队列中移除。

【补充】一般都是针对队列整体设置过期时间,而不是针对消息设置过期时间。

总结:

4.死信队列

如果某一个队列中有10条消息,其中只有1条消息单独设置了消息过期,即便消息已经过期了,队列中显示的依旧是存在10条消息,而不是9条,只有当消费者消费那条已经过期的消息时(此时消息会到达队列顶端),队列才会取判断那条消息是否过期,过期了就从队列中移除丢弃,如果此时这个队列绑定了一个死信队列,那么就将这条消息存储到死信队列中,死信队列又可以去和其他队列绑定,将这条过期消息存储到其他队列中。

介绍:

2个核心问题

1.普通队列如何绑定死信队列?

2.消息什么时候称为死信?

消息称为死信的3中情况:

死信的交换机队列和正常的交换机队列其实是没有区别的

生产者方配置文件:

rabbitmq.host=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=ZCJZCJ
rabbitmq.password=ZCJZCJ
rabbitmq.virtual-host=/zcj

绑定:

5

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <!--publisher-confirms==>开启确认模式-->
    <!--publisher-returns==>开启回退模式-->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>


    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
    <!--
    死信队列:
        1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)
        2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
        3.正常队列绑定死信交换机
              设置两个参数
                  *x-dead-letter-exchange:死信交换机名称
                  *x-dead-letter-routing-key:发送给死信交换机的routingkey
    -->
    <!--1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)-->
    <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
        <!--3.正常队列绑定死信交换机-->
        <rabbit:queue-arguments>
            <!--3.1.设置死信交换机名称-->
            <entry key="x-dead-letter-exchange" value="exchange_dlx"></entry>
            <!--3.2.设置发送给死信交换机的routingkey-->
            <entry key="x-dead-letter-routing-key" value="dlx.ZCJ"></entry>

            <!--4.1.设置队列过期时间ttl,5秒过期-->
            <entry key="x-message-ttl" value="5000" value-type="java.lang.Integer"></entry>
            <!--4.2.设置队列的长度限制x-max-length,Queue容器里面只能存10条消息-->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="test_exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="test_dlx.#" queue="test_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)-->
    <rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
    <rabbit:topic-exchange name="exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

5测试:

/**
 * 发送测试死信消息
 * 1.过期时间
 * 2.长度限制
 * 3.消息拒收
 */
@Test
public void testDlx(){
   /*
    1.测试过期时间,死信消息
    rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","死信");
    */
   /*
    //2.测试长度超过Queue容器限制,死信消息
    //测试结果:Queue容器只能存10条消息,一次发送20条消息有10条消息直接进入死信队列,剩下10条过期后进入死信队列
    for(int i = 0;i<=20;i++){
        rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","死信"+i);
    }*/
    //3.测试消息拒收,死信消息
    //注意:测试消息拒收时,消费者端一定不要让被拒收的消息重回Queue并让Queue重发,而是进入死信队列
    rabbitTemplate.convertAndSend("test_exchange_dlx","test_dlx.zcj","拒收");
}

第三种测试的注意注意点:

测试结果(仅展示第一种):

总结:

5.延迟队列

介绍:

生产者端

<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>

<!-- 定义rabbitmq connectionFactory -->
<!--publisher-confirms==>开启确认模式-->
<!--publisher-returns==>开启回退模式-->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                           port="${rabbitmq.port}"
                           username="${rabbitmq.username}"
                           password="${rabbitmq.password}"
                           virtual-host="${rabbitmq.virtual-host}"
                           publisher-confirms="true"
                           publisher-returns="true"
/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>


<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--
延迟队列:
    1.声明正常队列(order_queue)和交换机(order_exchange)
    2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
    3.正常队列绑定死信交换机
          设置两个参数
              *x-dead-letter-exchange:死信交换机名称
              *x-dead-letter-routing-key:发送给死信交换机的routingkey
-->
<!--1.声明正常队列(test_queue_dlx)和交换机(test_exchange_dlx)-->
<rabbit:queue name="order_queue" id="order_queue">
    <!--3.正常队列绑定死信交换机-->
    <rabbit:queue-arguments>
        <!--3.1.设置死信交换机名称-->
        <entry key="x-dead-letter-exchange" value="order_exchange_dlx"></entry>
        <!--3.2.设置发送给死信交换机的routingkey-->
        <entry key="x-dead-letter-routing-key" value="dlx_order.cancel"></entry>
        <!--3.3.设置队列过期时间ttl,5秒过期-->
        <entry key="x-message-ttl" value="5000" value-type="java.lang.Integer"></entry>
    </rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="order_exchange">
    <rabbit:bindings>
        <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
    </rabbit:bindings>
</rabbit:topic-exchange>

<!--2.声明死信队列(order_queue_dlx)和死信交换机(order_exchange_dlx)-->
<rabbit:queue name="order_queue_dlx" id="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange name="order_exchange_dlx">
    <rabbit:bindings>
        <rabbit:binding pattern="dlx_order.#" queue="order_queue_dlx"></rabbit:binding>
    </rabbit:bindings>
</rabbit:topic-exchange>

5测试

@Test
public void testDelay() throws InterruptedException {
    //1.发送订单消息。订单系统下单成功后,发送消息
    rabbitTemplate.convertAndSend("order_exchange","order.MSG","订单id=1");
    //2.倒计时10秒
    for (int i = 10;i>0;i--){
        System.out.println("倒计时:"+i);
        Thread.sleep(1000);
    }
}

5消费者端

<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>

<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                           port="${rabbitmq.port}"
                           username="${rabbitmq.username}"
                           password="${rabbitmq.password}"
                           virtual-host="${rabbitmq.virtual-host}"/>

<context:component-scan base-package="cn.zcj.rabbitmq.listener"></context:component-scan>
<!--定义监听器容器-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="10">
    <!--延迟队列实现,这里消费者监听的必然是死信队列-->
    <rabbit:listener ref="orderListener" queue-names="order_queue_dlx"/>
</rabbit:listener-container>

5

package cn.zcj.rabbitmq.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

@Component
public class OrderListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        //收到的当前消息的标签
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //1.接收转换消息
            System.out.println(new String(message.getBody()));
            //2.处理业务
            System.out.println("处理业务逻辑");
            System.out.println("1.根据订单id查询订单");
            System.out.println("2.判断订单是否支付成功");
            System.out.println("3.支付成功不做处理,支付失败回滚库存");
            //3.手动签收,true表示允许多条消息同时签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("出现异常,拒绝接收");
            channel.basicNack(deliveryTag,true,false);
        }
    }
}

5

@Test
public void testHelloWorld(){
    while (true){
    }
}

测试结果描述:

订单模块(生产者端)发送一个订单消息(库存-1)到订单队列(正常队列),订单队列里面的消息10秒后过期,过期的消息会因为订单队列绑定了死信队列而进入死信队列;库存模块(消费者端)会去监听订单队列绑定的那个死信队列,并将死信队列的消息取出来进行业务判断,如果订单已经支付,则不做处理,如果订单没有支付,就回滚库存(库存+1)。

项目结构:

5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值