RabbitMQ之个人记录

RabbitMQ

RabbitMQ有六种不同的工作模式

参考: https://www.rabbitmq.com/getstarted.html

www.rabbitmq---->Get Started------------>RabbitMQ Tutorials

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

创建工程(生产者、消费者)
添加依赖

给生产者和消费者配置maven坐标依赖

<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>
编写生产者发送消息
package cn.cdw.rabbitmq;

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

/*
消费者
 */
public class Producer {
    public static void main(String[] args)throws Exception {
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //设置访问ip
        connectionFactory.setHost("192.168.147.133"); //默认为localhost
        //设置访问端口号
        connectionFactory.setPort(5672); //默认端口号为5672
        //设置虚拟机
        connectionFactory.setVirtualHost("/cdw");//虚拟机,默认值为: /
        //设置登录用户
        connectionFactory.setUsername("cdw"); //用户名,默认为guest
        //密码
        connectionFactory.setPassword("cdw"); //密码: 默认为guest

        //创建连接
        Connection connection = connectionFactory.newConnection();
        //创建管道
        Channel channel = connection.createChannel();
        //通过channel创建队列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 参数
         */
        //如果没有一个名字叫rabbitmqQueueName的队列,则会创建该队列,如果有则不会创建
        channel.queueDeclare("rabbitmqQueueName", true, false, false, null);
        //通过channel发送消息
        /*
          basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
          参数详解:
          参数1: exchange 表示交换机名称。简单模式下交换机使用默认的
          参数2: routingKey 表示路由名称,如果使用默认的交换机,那么这个参数必须指定和队列名称一样
          参数3: props  表示配置信息
          参数4: body  表示消息数据
         */

        //body
        String body = "RabbitMQ Body TEST SUCCESS";
        channel.basicPublish("", "rabbitmqQueueName", null, body.getBytes());

        channel.close();
         connection.close();
    }
}

测试:

运行,然后访问 http://192.168.147.133:15672/#/queues 如图

在这里插入图片描述

编写消费者接收消息
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ消费端
 */
public class Consumer  {
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //设置HOST,默认为localhost
        connectionFactory.setHost("192.168.147.133");
        //设置PORT,默认为5672
        connectionFactory.setPort(5672);
        //设置虚拟机,默认为 /
        connectionFactory.setVirtualHost("/cdw");
        //设置username
        connectionFactory.setUsername("cdw");
        //设置password
        connectionFactory.setPassword("cdw");

        //创建连接
        Connection connection = connectionFactory.newConnection();

        //创建管道
        Channel channel = connection.createChannel();
        //创建队列
        channel.queueDeclare("rabbitmqQueueName", true, false, false, null);

       

        /*
       回调对象的处理
       回调方法,当收到消息后会自动执行该方法
       handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
       参数详解:
       参数1:consumerTag 表示标识
       参数2: envelope 表示可以用来获取一下信息,比如交换机的信息,路由的信息等
       参数3: properties  表示配置的信息
       参数4: body  表示数据
         */
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @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: " + body);
            }
        };
         //接收消息
        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数详解:
        参数1:queue 表示队列名称
        参数2: autoAck 表示自动确认。就比如说消费者收到消息了,就跟RabbitMQ说一下,我收到消息了
        参数3: callback 表示回调对象
         */
        channel.basicConsume("rabbitmqQueueName",true,consumer);
        //消费者是否关闭资源,当然是不关闭资源了,它应该要一直监听RabbitMQ消息,
        //不关闭资源,应该一直监听消息 channel.close(); connection.close();
    }
}

测试:

运行,看控制台的打印信息

RabbitMQ的工作模式

对于一些任务繁重,或任务较多的情况下使用工作模式可以提高任务处理的速度

一个生产者,两个消费者演示
生产者
package cn.cdw.rabbitmq;

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

/*
RabbitMQ工作模式消费端
 */
public class ProducerWorkQueue {
    public static void main(String[] args)throws Exception {
        //参数信息
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //connection
        Connection connection = connectionFactory.newConnection();
        //channel
        Channel channel = connection.createChannel();
        //queue
        channel.queueDeclare("workQueue", true, false, false, null);
        //发送消息
        /*
        因为RabbitMQ的工作模式,一个生产者两个消费者,所以我们使用循环生产多个数据,测试效果才看得出来
         */
        for (int i = 1; i <= 10; i++) {
            //消息
            String body = i + "workQueue SUCCESS";
            channel.basicPublish("", "workQueue", null, body.getBytes());
            System.out.println(body);
        }

        //关闭资源
        channel.close();
        connection.close();

    }
}

消费者1
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ工作模式的消费端1
 */
public class ConsumerWorkQueue1 {
    public static void main(String[] args)throws Exception {
        //设置信息
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //connection
        Connection connection = connectionFactory.newConnection();
        //channel
        Channel channel = connection.createChannel();
        //queue
        channel.queueDeclare("workQueue", true, false, false, null);

        //回调数据方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
            }
        };

        //接收消息
        channel.basicConsume("workQueue", true, consumer);
        //不关闭资源,一直监听RabbitMQ消息
    }
}

消费者2
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ工作模式消费端2
 */
public class ConsumerWorkQueue2 {
    public static void main(String[] args)throws  Exception {
        //设置信息ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //connection
        Connection connection = connectionFactory.newConnection();
        //channel
        Channel channel = connection.createChannel();
        //queue
        channel.queueDeclare("workQueue", true, false, false, null);

        //回调处理方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
            }
        };
            //接收消息
        channel.basicConsume("workQueue", true, consumer);
        //不关闭资源,一直监听RabbitMQ消息
    }
}

测试

先运行两消费者,消费者一直监听这RabbitMQ

然后启动生产者,看看消费者的控制台发现它们分担了处理

Pub/Sub 订阅模式

交换机需要和队列进行绑定,绑定之后;一个消息可以被多个消费者收到

生产者
package cn.cdw.rabbitmq;

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

import javax.management.Query;

/*
RabbitMQ的订阅模式
 */
public class ProducerPusSub {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //connection
        Connection connection = connectionFactory.newConnection();
        //channel
        Channel channel = connection.createChannel();

        //创建交换机
        /*
        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:参数
         */
        //交换机名称
        String exchangeName = "pubSubExchangeName";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);
        //创建队列
        //定义两个队列名称
        String queueName1 = "PubSubQueue1";
        String queueName2 = "pubSubQueue2";
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);

        //绑定队列交换机
        /*
        queueBind(String queue, String exchange, String routingKey)
        参数详解
        参数1: queue  表示队列的名称
        参数2: exchange 表示交换机的名称
        参数3:routingKey 表示路由键,绑定规则
            如果交换机的类型为FANOUT广播类型,那么routingKey设置为"" 空
         */
        channel.queueBind(queueName1, exchangeName, "");
        channel.queueBind(queueName2, exchangeName, "");

        //发送消息
        //body
        String body = "PubSub TEST SUCCESS";
        channel.basicPublish("pubSubExchangeName", "", null, body.getBytes());

        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ订阅模式消费者1
 */
public class ConsumerPubSub1 {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //connection
        Connection connection = connectionFactory.newConnection();
        //channel
        Channel channel = connection.createChannel();
        //创建Exchange
        String exchangeName = "pubSubExchangeName";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);
        //创建queue
        String queueName1 = "PubSubQueue1";
        //String queueName2 = "pubSubQueue2";
        channel.queueDeclare(queueName1, true, false, false, null);
        //channel.queueDeclare(queueName1, true, false, false, null);

        //绑定交换机
        channel.queueBind(queueName1, exchangeName, "");
        //channel.queueBind(queueName2, exchangeName, "");

        //接收消息
        //回调方法的处理
        DefaultConsumer consumer=new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                System.out.println("队列1,我让它执行findAll方法");
            }
        };
        channel.basicConsume(queueName1, true, consumer);
    }
}

消费者2
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

//RabbitMQ订阅模式消费者2

public class ConsumerPubSub2 {
    public static void main(String[] args)throws  Exception {
            //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();
        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //Exchange
        String exchangeName = "pubSubExchangeName";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);

        //queue
        String queueName2 = "PubSubQueue2";
        channel.queueDeclare(queueName2, true, false, false, null);

        //绑定交换机
        channel.queueBind(queueName2, exchangeName, "");

        //接收消息
        //处理回调对象方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                System.out.println("我让这个队列把数据存储到数据库中");
            }
        };
        //接收消息
        channel.basicConsume(queueName2, true, consumer);
    }
}

测试

启动所有的消费者

然后使用生产者发送消息,在每个消费者对应的控制台可以查看到生产者发送的所有消息

Routing 路由模式

Routing路由模式要求队列在绑定交换机是要指定routing key,消息会转发的符合的routing key的队列

生产者
package cn.cdw.rabbitmq;

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

/*
RabbitMQ路由模式生产者

 */
public class ProducerRouting {
    public static void main(String[] args) throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();

        //设置数据
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //Channel
        Channel channel = connection.createChannel();

        //Exchange , 注意:Exchange类型必须为Direct定向类型
        String exchangeName = "RoutingExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);

        //queue
        String queueName1 = "RoutingQueue1";
        String queueName2 = "RoutingQueue2";
        channel.queueDeclare(queueName1, true, false, false, null);
        channel.queueDeclare(queueName2, true, false, false, null);

        //绑定Exchange,测试队列1保存异常信息到数据看,所以定义它的路由名称为error
        //然后队列2 保存info warning error在控制台显示,所以队列2的路由名称为info,warning,error
        channel.queueBind(queueName1, exchangeName, "error");
        channel.queueBind(queueName2, exchangeName, "info");
        channel.queueBind(queueName2, exchangeName, "warning");
        channel.queueBind(queueName2, exchangeName, "error");

        //发送消息
        //队列1的消息:
        String body1 = "Routing TEST SUCCESS,把异常的信息保存到数据库ERROR";
        //队列2的消息
        String body2 = "Routing TEST SUCCESS,把info,warning,error的信息显示在控制台INFO,WARNING,ERROR";

        channel.basicPublish(exchangeName, "error", null, body1.getBytes());
        channel.basicPublish(exchangeName, "info", null, body2.getBytes());
        channel.basicPublish(exchangeName, "warning", null, body2.getBytes());
        channel.basicPublish(exchangeName, "error", null, body2.getBytes());

        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ的路由模式的消费者1
 */
public class ConsumerRouting1 {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();

        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //Exchange,路由模式的交换机类型必须为Direct
        String exchangeName = "RoutingExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);

        //queue
        String queueName1 = "RoutingQueue1";
        channel.queueDeclare(queueName1, true, false, false, null);

        //绑定Exchange
        channel.queueBind(queueName1, exchangeName, "error");

        //接收方法
        //回调对象的处理方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                System.out.println("测试我要把这个队列1的异常信息存储到数据库,ERROR");
            }
        };
        //接收
        channel.basicConsume(queueName1, true, consumer);
    }
}

消费者2
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ的路由模式的消费者2
 */
public class ConsumerRouting2 {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();

        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //Exchange
        String exchangeName = "RoutingExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);

        //queue
        String queueName2 = "RoutingQueue2";
        channel.queueDeclare(queueName2, true, false, false, null);

        //绑定Exchange
        channel.queueBind(queueName2, exchangeName, "error");
        channel.queueBind(queueName2, exchangeName, "info");
        channel.queueBind(queueName2, exchangeName, "warning");

        //接收消息,回调对象的处理方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
                System.out.println("测试我们将队列2把INFO,WARNING,ERROR的异常在控制台显示 INFO,WARNING,ERROR");
            }
        };
        //接收
        channel.basicConsume(queueName2, true, consumer);
    }
}

测试

启动所有消费者,然后使用生产者发送消息

查看控制台的打印信息

Topics通配符模式

Topic通配符模式可以实现PusSub发布与订阅模式和Routing路由模式

Topic通配符模式在路由名称上使用通配符,显得更加灵活

使用通配符:

*:表示一个词

例如:cdw.*

cdw.test1 可以匹配到

cdw.test1.test2 这不止一个词了,所以匹配不到

#:表示至少一个词和多个词

例如:cdw.#

dw.test1 可以匹配到

cdw.test1.test2 这不止一个词,所以也可以匹配到

cdw.# : 表示以cdw开头的都能匹配到

#.cdw:表示以cdw结尾的都能匹配到

生产者
package cn.cdw.rabbitmq;

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

/*
RabbitMQ通配符模式
 */
public class ProducerTopic {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();

        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //Exchange , Exchange类型必须指定Topic
        String exchangeName = "TopicExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);

        //注意,这里我没有定义queue和绑定Exchange,我们在消费端会定义队列和绑定,所以这里不用写也是可以的

        //发送消息
        String body1 = "Topic TEST SUCCESS  第一条消息 ERROR";
        String body2 = "Topic TEST SUCCESS  第二条消息 ERROR,WARNING,INFO";
        channel.basicPublish(exchangeName, "cdw.error", null, body1.getBytes());

        channel.basicPublish(exchangeName, "cdw.cn.error", null, body2.getBytes());
        channel.basicPublish(exchangeName, "cdw.cn.warning", null, body2.getBytes());
        channel.basicPublish(exchangeName, "cdw.cn.info", null, body2.getBytes());

        //关闭资源
        channel.close();
        connection.close();
    }
}

消费者1
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ匹配模式消费者1
 */
public class ConsumerTopic1 {
    public static void main(String[] args)throws Exception {
        //ConnectionFactory
        ConnectionFactory connectionFactory=new ConnectionFactory();

        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //exchange
        String exchangeName = "TopicExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);

        //queue
        String queueName1 = "TopicQueue1";
        channel.queueDeclare(queueName1, true, false, false, null);

        //绑定交换机
        channel.queueBind(queueName1, exchangeName, "cdw.*"); //表示匹配cdw.后只有一个词的,例如cdw.error

        //接收消息
        //回调对象的处理方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "utf-8"));
                System.out.println("这是一个异常信息,我要把它存入到数据库");
            }
        };
        //接收
        channel.basicConsume(queueName1, true, consumer);
    }
}

消费者2
package cn.cdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/*
RabbitMQ匹配模式消费者2

 */
public class ConsumerTopic2 {
    public static void main(String[] args) throws  Exception{
        //ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();

        //设置信息
        connectionFactory.setHost("192.168.147.133");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/cdw");
        connectionFactory.setUsername("cdw");
        connectionFactory.setPassword("cdw");

        //Connection
        Connection connection = connectionFactory.newConnection();

        //channel
        Channel channel = connection.createChannel();

        //Exchange
        String exchangeName = "TopicExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);

        //queue
        String queueName2 = "TopicQueue2";
        channel.queueDeclare(queueName2, true, false, false, null);

        //绑定Exchange
        channel.queueBind(queueName2, exchangeName, "cdw.#");//表示陪陪cdw.至少一个词和多个词的,例如,cdw.cn.error,cdw.cn.info,chw.cn.warning

        //接收消息
        //回调对象的处理方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body, "utf-8"));
                System.out.println("这个队列2,我让它把ERROR,INFO,WARNING信息在控制台显示");
            }
        };
        //接收
        channel.basicConsume(queueName2, true, consumer);
    }
}

测试

以上RabbitMQ的五种模式 总结

简单模式(HelloWorld)

一个生产者,一个消费者,不需要设置交换机(使用默认交换机)

工作队列模式(Work Queue)

一个生产者,多个消费者(竞争关系),不需要设置 交换机(使用默认交换机)

发布订阅模式(Publish/Subscribe)

需要设置类型为FANOUT的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机将消息发送到绑定的队列

路由模式 (Routing)

需要设置类型为DIRECT的交换机,交换机和队列进行绑定,并且知道routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

通配符模式(Topic)

需要设置类型为TOPIC的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机很根据routing key将消息发送到对应的队列

Spring整合RabbitMQ

创建生产者

新建生产者和消费者工程
配置生产者和消费者引入maven坐标
 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

        <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>
编写配置文件RabbitMQ的连接参数(rabbitmq.properties),生产者和消费者都需要配置
rabbitmq.host=192.168.147.133
rabbitmq.port=5672
rabbitmq.username=cdw
rabbitmq.password=cdw
rabbitmq.virtual-host=/cdw
生产者的配置文件(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,名字为:"",路由键为队列的名称
    -->
    <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: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="cdw.*" queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="cdw.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="cn.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
发送消息测试
package cn.cdw.rabbitmq.producer;

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 ProducerTest {
    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
    测试发送消息
     */
    @Test
    public void QueueTest() {
        rabbitTemplate.convertAndSend("spring_queue", "RabbitMQ的简单模式,只发送队列spring_queue的消息");
    }

    /*
    发送广播
    参数1:交换机的名称
    参数2:路由名称,广播的话,路由名称为空
    参数3:数据内容
     */
    @Test
    public void fanoutTest() {
        rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "发送的广播消息,发送到spring_fanout_exchange交换机广播消息");
    }

    /*
    通配符topic发送消息
   参数1:交换机名称
   参数2:路由键名
   参数3:发送消息内容
     */
    @Test
    public void topicTest() {
        rabbitTemplate.convertAndSend("spring_topic_exchange","cdw.test","使用通配符发送的消息,发送到spring_topic_exchange交换机的cdw.test");
        rabbitTemplate.convertAndSend("spring_topic_exchange","cdw.test.cn","使用通配符发送的消息,发送到spring_topic_exchange交换机的cdw.test.cn");
        rabbitTemplate.convertAndSend("spring_topic_exchange","cn.test.cdw","使用通配符发送的消息,发送到spring_topic_exchange交换机的cn.test.cdw");
    }
}

创建消费者工程

导入maven坐标
创建配置文件
rabbitmq.propreties
rabbitmq.host=192.168.147.133
rabbitmq.port=5672
rabbitmq.username=cdw
rabbitmq.password=cdw
rabbitmq.virtual-host=/cdw
spring-rabbitmq-consumer.xml

编写代码(必须实现MessageListener)

和配置文件的要对上

然后重新onMessage方法,然后直接打印就可以

package cn.cdw.rabbitmq.listener;


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

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

    }
}

然后写有一个测试方法,让它一直不停

package cn.cdw.rabbitmq.listener;

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 QueueTest {
    @Test
    public void queueTest() {
        boolean flag=true;
        while (true) {

        }
    }
}

Spring Boot整合RabbitMQ

生产者:

创建springboot工程

新建一个maven工程

引入依赖坐标
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <dependencies>
        <!--RabbitMQ的启动器依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--测试环境依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

编写yaml配置,基本信息配置

#配置RabbitMQ的基本信息 ip,端口,虚拟机,用户名,密码
spring:
  rabbitmq:
    host: 192.168.147.133 
    port: 5672
    virtual-host: /cdw
    username: cdw
    password: cdw

定义交换机,队列及绑定关系的配置类
package cn.cdw.producer.config;

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

/*
RabbitMQ配置类
 */
@Configuration
public class RabbitMQConfig {
    //交换机名称
    public static final String topicExchangeName = "SpringBootTopicExchange";
    //队列名称
    public static final String queueName = "SpringBootQueue";

    //声明交换机进行注册到Bean中
    @Bean("topicExchange")
    public Exchange topicExchange() {
        return ExchangeBuilder.topicExchange(topicExchangeName).durable(true).build();
    }

    //声明队列注册到Bean中
    @Bean("queue")
    public Queue queue() {
        return QueueBuilder.durable(queueName).build();
    }

    //绑定队列和交换机
    @Bean
    public Binding queueExchange(@Qualifier("queue") Queue queue, @Qualifier("topicExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("cdw.#").noargs();
    }
}

然后编写一个测试类

注入RabbitmqTemplate,调用方法,完成消息发送

package cn.cdw.producer;

import cn.cdw.producer.config.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 RabbitMQConfigTest {
    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    //编写一个测试方法发送消息
    @Test
    public void sendTest() {
        String message = "Spring Boot TEST SUCCESS";
        rabbitTemplate.convertAndSend(RabbitMQConfig.topicExchangeName, "cdw.test", message);
    }
}

​ 然后运行测试方法,查看RabbitMQ中是否有交换机,队列和消息

消费端编写

创建消费者工程
导入依赖坐标
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <dependencies>
        <!--RabbitMQ的启动器依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--测试环境依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
编写启动器类和yml配置文件
启动器类
package cn.cdw.consumer;

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

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

yml配置文件
#配置RabbitMQ的基本信息 ip,端口,虚拟机,用户名,密码
spring:
  rabbitmq:
    host: 192.168.147.133 
    port: 5672
    virtual-host: /cdw
    username: cdw
    password: cdw
定义监听器,使用@RabbitListener注解完成队列监听
package cn.cdw.consumer;

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

@Component
public class MyListener {

    /*
    编写一个方法,使用注解@RabbitListener标准监听的队列

     */
    @RabbitListener(queues = "SpringBootQueue")
    public void myListener(String message) {
        System.out.println(message);

    }
}

然后运行消费者的启动器类

如果没有数据了,就使用生产者生产数据,然后在启动消费者

RabbitMQ的高级特性

消息可靠性传递

杜绝任何消息丢失或传递失败场景

RabbitMQ提供了两种方式用来控制消息的投递可靠性模式

confirm 确认模式

return 退回模式

实现
创建消费者和生产者工程
给生产者和消费者导入依赖坐标
  <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

        <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>
生产者和消费者的RabbitMQ的基本配置信息
rabbitmq.properties
rabbitmq.host=192.168.147.133
rabbitmq.port=5672
rabbitmq.username=cdw
rabbitmq.password=cdw
rabbitmq.virtual-host=/cdw
编写生产者配置文件
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}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

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

    <!--========================================================================================================================================-->
    <!--定义消息的可靠性传递(生产端)-->
    <!--name属性表示队列queue的名称-->
    <rabbit:queue id="test_queue_confirm" name="test_confirm_queueName"></rabbit:queue>

    <!--定义一个交换机-->
    <rabbit:direct-exchange name="test_exchange_confirm_exchangeName">
        <!--将队列和交换机绑定-->
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm_routingKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>
</beans>
编写测试类
Confirm确认模式代码实现

消息从Producer到Exchange,则会返回一个confirmCallBack

package cn.cdw.producer;

import org.junit.Test;
import org.junit.runner.RunWith;
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:rabbitmq-producer.xml")
public class ProducerTest {

    //注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
    确认模式
        步骤:
            确认模式的开启: ConnectionFactory中开启
                在配置文件中配置  publisher-confirms="true" ,开启确认模式,它模式是false的
            使用RabbitTemplate定义ConfirmCallBack回调函数
                true 表示发送消息到交换机成功
                false 表示发送消息到交换机失败


     */
    //编写一个测试方法
    @Test
    public void test() {
        //使用RabbitTemplate定义ConfirmCallBack回调函数
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                /*
                confirm(CorrelationData correlationData, boolean ack, String cause)
                参数详解:
                    参数1:correlationData   相关的配置信息
                    参数2: ack  exchange交换机是否成收到消息。true 成功,false失败
                    参数3: cause  失败的原因
                 */
                if (ack) {
                    //exchange接收消息成功
                    System.out.println("exchange接收消息成功: " + cause);
                }else {
                    //如果想测试它失败,我们只需要修改下面的发送消息的交换机的名称不存在的就可以,因为它没法发送消息到交换机,所以当然失败了
                    System.out.println("exchange接收消息失败: " + cause);
                    //做一些处理,让消息再次发送
                }
                System.out.println("confirm方法被执行了。。。。");
            }
        });

        //测试发送一条消息,上面confirm方法是否执行
        String message = "Confirm TEST SUCCESS ,我已经发送了消息";
        //使用RabbitTemplate给交换机发送一条消息

        rabbitTemplate.convertAndSend("test_exchange_confirm_exchangeName", "confirm_routingKey", message);
        //rabbitTemplate.convertAndSend("test_exchange_confirm_exchangeName_test", "confirm_routingKey", message);
    }
}

Return退回模式代码实现

当消息发送给Exchange后,Exchange路由到Queue失败时才会执行ReturnBack

 /*
    测试Return回退模式
     */
    @Test
    public void testReturn() {
        /*
        当消息发送给Exchange后,Exchange路由到Queue失败时,才会执行ReturnCallBack
        步骤
            开启回退模式
                在配置文件中设置开启 publisher-returns="true"
            设置ReturnCallBack
            设置Exchange处理消息的模式(两种模式)
                如果消息没有路由到Queue,则丢弃消息(默认)
                如果消息没有路由到Queue,返回消息发送方,就是ReturnCallBack
         */

        //设置交换机处理失败消息的模式
        rabbitTemplate.setMandatory(true);//设为true,它就不会执行默认的把消息丢弃了,而是将消息返回消息发送方

        //使用RabbitTemplate定义ReturnCallBack回调函数
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                //如果正常这么测试,这句话是不会执行到的,因为下面发送消息到Exchange到Queue都是正常的
                //测试的话,我们可以把它路由的名称改为一个不存在的,达到Exchange发送不到Queue不就可以了,
                //但是是达到模式了,它还是没有打印出这句话 ,因为我们没有设置它的处理模式,默认它就把消息给丢弃了,所以也不会执行这个方法
                //所以我们需要设置处理的模式 rabbitTemplate.setMandatory(true)

                /*
                returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey)
                参数详解:
                参数1: message  表示消息对象
                参数2: replyCode  表示回复的错误码
                参数3:replyText  表示回复的错误的信息
                参数4:exchange  表示交换机
                参数5:routingKey 表示路由键
                 */
                //我们把这些参数都打印出来看看
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);

                System.out.println("return方法被执行了。。。");

            }
        });
        //使用RabbitTemplate发送消息
        String message = "Return TEST SUCCESS , 回退模式测试...";
       // rabbitTemplate.convertAndSend("test_exchange_confirm_exchangeName", "confirm_routingKey", message);
        rabbitTemplate.convertAndSend("test_exchange_confirm_exchangeName", "confirm_routingKey_test", message);
    }

Consumer ACK

ack指 Acknowledge,确认。 表示消费端收到消息后的确认方式。

有三种确认方式:

• 自动确认:acknowledge=“none

• 手动确认:acknowledge=“manual

• 根据异常情况确认:acknowledge=“auto

创建消费端

创建

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.7.RELEASE</version>
        </dependency>

        <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>

编写RabbitMQ的基本信息的配置文件rabbitmq.properties

rabbitmq.host=192.168.147.133
rabbitmq.port=5672
rabbitmq.username=cdw
rabbitmq.password=cdw
rabbitmq.virtual-host=/cdw
自动签收

编写消费端的配置文件consumer-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,我创建一个包,专门放置监听器,然后我们去扫描这些包,就加载到bean了,这样我们就不需要定义多个bean了-->
    <!--然后我只需要在监听器上使用注解@Component标注为是一个组件,包扫描就可以扫描出来了-->
    <context:component-scan base-package="cn.cdw.consumer.listener"></context:component-scan>

    <!--定义监听器-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="ackListener" queue-names="test_confirm_queueName"></rabbit:listener>
    </rabbit:listener-container>
</beans>

编写监听器

package cn.cdw.consumer.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.stereotype.Component;
/*
Consumer ACK机制

 */
//标注为是一个组件
@Component
public class AckListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        System.out.println(new String(message.getBody()));
    }
}

编写测试类,启动监听器监听MQ队列

package cn.cdw.consumer;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/*
编写一个测试方法,让消费者一直运行着的,所以使用while(true)
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:rabbitmq-consumer.xml")
public class ConsumerTest {
    @Test
    public void test() {
        while (true) {

        }
    }
}

测试,启动测试类,然后启动生产者生产数据

手动签收

配置文件

<?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,我创建一个包,专门放置监听器,然后我们去扫描这些包,就加载到bean了,这样我们就不需要定义多个bean了-->
    <!--然后我只需要在监听器上使用注解@Component标注为是一个组件,包扫描就可以扫描出来了-->
    <context:component-scan base-package="cn.cdw.consumer.listener"></context:component-scan>

    <!--定义监听器-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
        <!--自动签收监听器-->
      <!--  <rabbit:listener ref="ackListener" queue-names="test_confirm_queueName"></rabbit:listener> -->
        <!--手动签收监听器-->
        <rabbit:listener ref="ackListenerManual" queue-names="test_confirm_queueName"></rabbit:listener>
    </rabbit:listener-container>
</beans>

监听器

package cn.cdw.consumer.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;

/*
Consumer ACK机制
    设置手动签收: 在配置文件中配置 acknowledge="manual"
    让监听器实现ChannelAwareMessageListener这个接口,重写onMessage方法
    如果消息成功处理,则调用channel的basicAck()签收
    如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
 */
@Component
public class AckListenerManual implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);
        //获取deliveryTag
        Long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            //接收转换消息
            System.out.println(new String(message.getBody()));
            //处理业务逻辑
            System.out.println("处理业务");
            //我们在这里设置一个异常,测试处理业务出错了
            int i = 1 / 0;
            //手动签收
        /*
        basicReject(long deliveryTag, boolean multiple)
        参数1:deliveryTag   获取deliveryTay     Long deliveryTag = message.getMessageProperties().getDeliveryTag()
        参数2: multiple   是否需要签收多条消息
         */
            channel.basicAck(deliveryTag, true);

            //这就是消息处理成功的正常签收,如果消息处理失败,我们应该拒绝签收,所以我们这里使用try{}catch(){}包裹起来
        } catch (Exception e) {
            // e.printStackTrace();
            //拒绝签收
            /*
            basicNack(long deliveryTag, boolean multiple, boolean requeue)
            参数1: deliveryTag   获取deliveryTay     Long deliveryTag = message.getMessageProperties().getDeliveryTag()
            参数2:  multiple   是否需要签收多条消息
            参数3:requeue  表示重回队列,如果设置为true,则消息重新回到queue,broker会重新发送给消息给消费端

             */
            channel.basicNack(deliveryTag, true, true);
            //还有一种方法,但是还是推荐使用上面这个方法
            /*
            basicReject(long deliveryTag, boolean requeue)
             */
            //channel.basicReject(deliveryTag, true);
            System.out.println("处理业务出错了,我要把消息返回给queue了,你重新再给我发消息吧。。");
        }
    }
}

测试

总结
  • 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认

  • 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息

  • 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。

消费端限流

编写配置文件

<?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,我创建一个包,专门放置监听器,然后我们去扫描这些包,就加载到bean了,这样我们就不需要定义多个bean了-->
    <!--然后我只需要在监听器上使用注解@Component标注为是一个组件,包扫描就可以扫描出来了-->
    <context:component-scan base-package="cn.cdw.consumer.listener"></context:component-scan>

    <!--定义监听器-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
        <!--自动签收监听器-->
      <!--  <rabbit:listener ref="ackListener" queue-names="test_confirm_queueName"></rabbit:listener> -->
        <!--手动签收监听器-->
      <!--  <rabbit:listener ref="ackListenerManual" queue-names="test_confirm_queueName"></rabbit:listener>-->
        <!--消费端限流-->
        <rabbit:listener ref="qosListener" queue-names="test_confirm_queueName"></rabbit:listener>
    </rabbit:listener-container>
</beans>

限流监听器

package cn.cdw.consumer.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;

/*
Consumer 限流机制
    编写ack机制为ack手动确认 acknowledge="manual"
    listener-container配置属性
        prefetch = 1 , 表示消费端每次从mq中拉取一条消息来消费,直到手动确认消费完毕,才会继续拉取下一条消息。
 */
@Component
public class QosListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //获取消息
        System.out.println(new String(message.getBody()));
        //处理业务逻辑
        System.out.println("处理业务逻辑");
        //签收,测试我先不手动签收(把它注释掉),我设置了消费端限流每次从mq中拉去一条消息,当消费端全部消费完后进行签收后,才会再去mq中拉去一条消息,
        //测试结果这里只会拉取一条消息,因为我这里我没有手动签收,所以它不会去拉取下一条消息,
        //channel.basicAck(deliveryTag, true);
        //还有不签收,其实这里使用try,catch包裹的,测试我就不写了
        //我们在消费端写一个方法,使用循环发送多条数据到mq中进行消费端限流的测试
    }
}

测试

小结

  • 在rabbit:listener-container 中配置 prefetch属性设置消费端一次拉取多少消息

  • 消费端的确认模式一定为手动确认。acknowledge=“manual”

TTL(主要是生产端)

TTL全称Time To Live(存活时间、过期时间)。当消息到达存活时间后,会被自动删除。RabbitMQ可以对消息设置过期时间,也可以对整个queue设置过期时间

在RabbitMQ管理控制台设置过期时间
代码实现设置过期时间
队列过期时间实现

编写配置文件

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

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

    <!--========================================================================================================================================-->
    <!--定义消息的可靠性传递(生产端)-->
    <!--name属性表示队列queue的名称-->
    <rabbit:queue id="test_queue_confirm" name="test_confirm_queueName"></rabbit:queue>

    <!--定义一个交换机-->
    <rabbit:direct-exchange name="test_exchange_confirm_exchangeName">
        <!--将队列和交换机绑定-->
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm_routingKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    
    <!--TTL Queue-->
    <rabbit:queue id="test_queue_ttl" name="test_queueName_ttl">
      <rabbit:queue-arguments>
          <!--设置怎个队列的消息存活时间,这里我设置了10秒-->
          <!--注意,它的值是number类型的,所以我们需要指定它的value类型-->
          <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
      </rabbit:queue-arguments>
    </rabbit:queue>
    
    <!--TTL Exchange-->
    <rabbit:topic-exchange name="test_exchangeName_ttl">
        <!--绑定queue-->
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

发送消息

  /*
    TTL:过期时间
        队列过期时间

     */
    @Test
    public void ttlTest() {
        String message = "TTL TEST SUCCESS,生产端已经发送了消息...";
        for (int i = 1; i <= 10; i++) {
            rabbitTemplate.convertAndSend("test_exchangeName_ttl", "ttl.cdw", message);
        }
    }

查看RabbitMQ是否10秒后将队列的索引消息的清除了

消息的单独过期

编码

 /*
    TTL:过期时间
    消息单独过期时间
        如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准
        队列过期后,会将队列所有消息移除

     消息过期后,只有在消息队列的顶端,才会判断其是否过期(移除掉)
        就比如说,一共有5条消息,前面有我设置第3条消息5秒后过期,前面两条消息不过期,这时过5秒后,第3条消息是过期了,但是在队列中还是看到的(没有移除掉)
        只有当它在消息队列的顶端,才会移除掉

     */
    @Test
    public void ttlTest2() {

        //消息处理对象MessagePostProcessor
        /*
        设置一些消息的参数信息
            设置message消息
            返回该消息
         */
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置message信息
                message.getMessageProperties().setExpiration("5000");//设置消息的过期时间,它的类型是字符串。我这里我设置了5秒过期
                //返回该消息
                return message;

                //然后把我们在外面单独声明的消息处理方法,添加到发送消息里边参数去
            }

        };
        String message = "TTL TEST SUCCESS,生产端已经发送了消息...";
        rabbitTemplate.convertAndSend("test_exchangeName_ttl", "ttl.cdw", message,
                /* new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        return null;
                    }
                } */
                //上这样写不太直观,我们把消息处理对象单独声明出来,messagePostProcessor这个消息处理的参数,我们在上单独声明了处理方法了,这个把它传过来就可以刻
                messagePostProcessor);
    }

小结

  • 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。

  • 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。

  • 如果两者都进行了设置,以时间短的为准。

死信队列(DLX)

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

消息成为死信的三种情况
  1. 队列消息长度到达限制;
    1. 就比如说,我设置了这个队列只能存储10条消息,长度为10,当第11条消息过来的时候,这个第11条消息就成为了死信
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费
队列绑定死信交换机

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

配置文件

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

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

    <!--========================================================================================================================================-->
    <!--定义消息的可靠性传递(生产端)-->
    <!--name属性表示队列queue的名称-->
    <rabbit:queue id="test_queue_confirm" name="test_confirm_queueName"></rabbit:queue>

    <!--定义一个交换机-->
    <rabbit:direct-exchange name="test_exchange_confirm_exchangeName">
        <!--将队列和交换机绑定-->
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm_routingKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>


    <!--TTL Queue-->
    <rabbit:queue id="test_queue_ttl" name="test_queueName_ttl">
      <rabbit:queue-arguments>
          <!--设置怎个队列的消息存活时间,这里我设置了10秒-->
          <!--注意,它的值是number类型的,所以我们需要指定它的value类型-->
          <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
      </rabbit:queue-arguments>
    </rabbit:queue>
    
    <!--TTL Exchange-->
    <rabbit:topic-exchange name="test_exchangeName_ttl">
        <!--绑定queue-->
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--
        死信队列
            声明正常的队列(test_queueName_dlx)和交换机(test_exchangeName_dlx)
            声明死信队列queueName_dlx)和死信交换机(exchangeName_dlx)
            正常队列绑定死信交换机
                设置两个参数:
                    x-dead-letter-exchange  表示死信交换机名称
                    x-dead-letter-routing-key  发送给死信交换机的routingKey
    -->

    <!--声明正常的队列-->
    <rabbit:queue id="test_queue_dlx" name="test_queueName_dlx">
        <!--设置正常队列绑定死信交换机-->
        <rabbit:queue-arguments>
            <!--x-dead-letter-exchange  表示死信交换机名称-->
            <entry key="x-dead-letter-exchange" value="exchangeName_dlx"></entry>
            <!--x-dead-letter-routing-key  发送给死信交换机的routingKey-->
            <entry key="x-dead-letter-routing-key" value="dlx.cdw"></entry>

            <!--
                怎么让一个消息变为死信,三种方式
                    设置队列过期时间 ttl
                    设置长度限制 max-length
            -->
            <!--设置队列过期时间-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>

            <!--设置长度限制,我这里设置这个队列最大能存储10条消息,当存储到第11条消息时,第11条消息就是死信消息了-->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <!--定义正常交换机-->
    <rabbit:topic-exchange name="test_exchangeName_dlx">
        <!--将正常的队列绑定到正常的交换机-->
        <rabbit:bindings>
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--声明死信队列-->
    <rabbit:queue id="queue_dlx"  name="queueName_dlx"></rabbit:queue>
    <!--声明死信交换机-->
    <rabbit:topic-exchange name="exchangeName_dlx">
        <!--将死信队列绑定到死信交换机-->
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

编写测试代码

  /*
    死信队列
        过期时间
        长度限制
        消息拒收
     */
    //过期时间测试
    @Test
    public void dlxTest() {
        //测试过期时间,死信消息
        //发送消息到到正常交换机然后发送到正常队列,这里我设置了10秒这个队列过期,10秒后呢,它将成为死信消息,会发送到死信队列中
        String message = "Dlx TEST SUCCESS ,我生产端已经发送消息了...";
        rabbitTemplate.convertAndSend("test_exchangeName_dlx", "test.dlx.cdw", message);
    }

    //长度限制测试
    @Test
    public void dlxTest2() {
        //我在配置文件中设置了这个队列只能存储10条消息,超过10条消息后,超过得消息将成为死信消息劝,会发送到死信队列,我在配置中配置有过10秒整个队列过期,10秒后所有的消息将都发送到死信队列
        //这里我使用循环发送20条数据
        for (int i = 1; i <= 20; i++) {
            String message = i + "  :DLX TEST SUCCESS,我会成为死信吗?";
            rabbitTemplate.convertAndSend("test_exchangeName_dlx", "test.dlx.cn", message);
        }
    }

    //消息的拒收
    /*
    我们需要编写消费端拒绝接收消息,当处理业务出现异常时,就需要设置拒绝签收
    注意:消费者拒绝接收, 消息不重回队列 requeue=false,这样它才会进入死信队列
     */
    @Test
    public void dlxTest3() {
        String message = "DLX TEST SUCCESS,消费端发送消息了";
        rabbitTemplate.convertAndSend("test_exchangeName_dlx", "test.dlx.cn", message);
    }

消息拒签的消费端代码

配置文件

 <!--测试死信队列,拒收消息监听器-->
        <rabbit:listener ref="dlxListener" queue-names="test_queueName_dlx"></rabbit:listener>

拒收监听器代码

package cn.cdw.consumer.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 DlxListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.println(new String(message.getBody()));
            //手动制作一个异常
            int i=1/0;
            System.out.println("处理业务");
            //签收
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {
           // e.printStackTrace();
            System.out.println("出现异常了,这里拒绝签收。。。");
            //拒绝签收,注意,不重回队列 requeue=false;
            channel.basicNack(deliveryTag,true,false);
        }
    }
}

测试

  1. 死信交换机和死信队列和普通的没有区别
  2. 当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
    1. 如果没有绑定死信交换机,那么消息过期后就丢了
  3. 消息成为死信的三种情况:
    • 队列消息长度到达限制;

    • 消费者拒接消费消息,并且不重回队列;

    • 原队列存在消息过期设置,消息到达超时时间未被消费;

延迟队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

注意:在RabbitMQ中并未提供延迟队列功能。

但是可以使用:TTL+死信队列 组合实现延迟队列的效果。

提出需求:

  1. 下单后,30分钟未支付,取消订单,回滚库存。
实现

编写配置

<?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}"
                               publisher-confirms="true"
                               publisher-returns="true"
    />
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

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

    <!--========================================================================================================================================-->
    <!--定义消息的可靠性传递(生产端)-->
    <!--name属性表示队列queue的名称-->
    <rabbit:queue id="test_queue_confirm" name="test_confirm_queueName"></rabbit:queue>

    <!--定义一个交换机-->
    <rabbit:direct-exchange name="test_exchange_confirm_exchangeName">
        <!--将队列和交换机绑定-->
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm_routingKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>


    <!--TTL Queue-->
    <rabbit:queue id="test_queue_ttl" name="test_queueName_ttl">
      <rabbit:queue-arguments>
          <!--设置怎个队列的消息存活时间,这里我设置了10秒-->
          <!--注意,它的值是number类型的,所以我们需要指定它的value类型-->
          <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
      </rabbit:queue-arguments>
    </rabbit:queue>
    
    <!--TTL Exchange-->
    <rabbit:topic-exchange name="test_exchangeName_ttl">
        <!--绑定queue-->
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--
        死信队列
            声明正常的队列(test_queueName_dlx)和交换机(test_exchangeName_dlx)
            声明死信队列queueName_dlx)和死信交换机(exchangeName_dlx)
            正常队列绑定死信交换机
                设置两个参数:
                    x-dead-letter-exchange  表示死信交换机名称
                    x-dead-letter-routing-key  发送给死信交换机的routingKey
    -->

    <!--声明正常的队列-->
    <rabbit:queue id="test_queue_dlx" name="test_queueName_dlx">
        <!--设置正常队列绑定死信交换机-->
        <rabbit:queue-arguments>
            <!--x-dead-letter-exchange  表示死信交换机名称-->
            <entry key="x-dead-letter-exchange" value="exchangeName_dlx"></entry>
            <!--x-dead-letter-routing-key  发送给死信交换机的routingKey-->
            <entry key="x-dead-letter-routing-key" value="dlx.cdw"></entry>

            <!--
                怎么让一个消息变为死信,三种方式
                    设置队列过期时间 ttl
                    设置长度限制 max-length
            -->
            <!--设置队列过期时间-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>

            <!--设置长度限制,我这里设置这个队列最大能存储10条消息,当存储到第11条消息时,第11条消息就是死信消息了-->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <!--定义正常交换机-->
    <rabbit:topic-exchange name="test_exchangeName_dlx">
        <!--将正常的队列绑定到正常的交换机-->
        <rabbit:bindings>
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--声明死信队列-->
    <rabbit:queue id="queue_dlx"  name="queueName_dlx"></rabbit:queue>
    <!--声明死信交换机-->
    <rabbit:topic-exchange name="exchangeName_dlx">
        <!--将死信队列绑定到死信交换机-->
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>


    <!--
        延迟队列(TTL+DLX组合实现)
            定义正常交换机(order_exchangeName)和队列(order_queueName)
            定义死信交换机(order_exchangeName_dlx)和队列(order_queueName_dlx)
            绑定正常队列到死信交换机
                设置两个参数:
                    x-dead-letter-exchange  表示死信交换机名称
                    x-dead-letter-routing-key  发送给死信交换机的routingKey
    -->

    <!--定义正常queue-->
    <rabbit:queue id="order_queue" name="order_queueName">
        <!--绑定死信交换机-->
        <rabbit:queue-arguments>
            <entry key="x-dead-letter-exchange" value="order_exchangeName_dlx"></entry>
            <entry key="x-dead-letter-routing-key" value="order.dlx.cancel"></entry>
            <!--设置队列过期时间,我们实现的需求是30分钟,但是我们为了测试,就写10秒测试了-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <!--定义正常的exchange-->
    <rabbit:topic-exchange name="order_exchangeName">
        <!--绑定正常的队列-->
        <rabbit:bindings>
            <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义死信队列-->
    <rabbit:queue id="order_queue_dlx" name="order_queueName_dlx"></rabbit:queue>
    <!--定义死信交换机-->
    <rabbit:topic-exchange name="order_exchangeName_dlx">
        <!--绑定死信队列-->
        <rabbit:bindings>
            <rabbit:binding pattern="order.dlx.#" queue="order_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

编写生产端

 /*
    队列延迟
       注意,我们需要先把队列和交换机创建出来(随便执行一个测试方法发送一条消息即可),要不然后面我们先启动消费者没有找到队列和交换机就报错了
     */
    @Test
    public void delayTest()throws Exception {
      //模拟发送订单信息,将来是在订单系统中,下单成功后,发送消息
        String message="订单信息: id=1,time=2019年12月30日19:41:56";
        rabbitTemplate.convertAndSend("order_exchangeName", "order.msg", message);

        //打印倒计时10秒
        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            Thread.sleep(1000);
        }
}

编写消费端监听器

package cn.cdw.consumer.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{
            System.out.println(new String(message.getBody()));
            System.out.println("处理业务逻辑");
            System.out.println("根据订单id查询其状态");
            System.out.println("判断状态是否为支付成功");
            System.out.println("取消订单,回滚库存");

            //签收
            channel.basicAck(deliveryTag, true);
        }catch (Exception e){
//            e.printStackTrace();
            System.out.println("出现异常,拒绝签收");
            //拒签
            channel.basicNack(deliveryTag, true, true);
        }
    }
}

配置注册消费端监听器

<!--
        消费端订单监听器
        延迟队列效果实现:一定要监听的是死信队列
            注意:它应该监听的是这个死信的队列,因为这个死信队列有延迟的功能
            如果监听正常的队列那么,消息一来就到了,没启动需求的作用
        -->
        <rabbit:listener ref="orderListener" queue-names="order_queueName_dlx"></rabbit:listener>

测试:启动消费端,再启动生产端,注意控制台打印,消费端需要等待10秒才会收到死信队列的消息,因为死信队列有延迟队列功能(TTL+DLX实现)

小结

  1. 延迟队列 指消息进入队列后,可以被延迟一定时间,再进行消费。

  2. RabbitMQ没有提供延迟队列功能,但是可以使用 : TTL + DLX 来实现延迟队列效果。

日志与监控

RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log

查看虚拟机命令: rabbitmqctl list_vhosts

查看连接命令: rabbitmqctl list_connections

查看exchanges:rabbitmqctl list_exchanges

查看消费者信息:rabbitmqctl list_consumers

查看环境变量:rabbitmqctl environment

查看未被确认的队列:rabbitmqctl list_queues name messages_unacknowledged

查看单个队列的内存使用:rabbitmqctl list_queues name memory

查看准备就绪的队列:rabbitmqctl list_queues name messages_ready

消息追踪

注意使用guest用户,虚拟机使用 / 默认的

在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。对于RabbitMQ而言,可能是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。这个时候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和

在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。维人员进行问题的定位。

消息追踪-Firehose

将队列绑定到amq.rabbitmq.trace,路由key为#,所有的请求消息它都发送

rabbitmqctl trace_on:开启Firehose命令

​ 注意:打开 trace 会影响消息写入功能,适当打开后请关闭

建议:在开发阶段我们可以开启消息追踪,在实际生产环境建议将其关闭

rabbitmqctl trace_off:关闭Firehose命令

我们向队列发送一条消息

我们发现当前消息也正常存在,并且开启消息追踪后,会多出一条消息是 amq.rabbitmq.trace 交换机发给当前队列的消息,消息中的内容是比较完整的

消息追踪-rabbitmq_tracing

rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose多了一层GUI的包装,更容易使用和管理。

启用插件:rabbitmq-plugins enable rabbitmq_tracing

查看插件命令:

先输入命令:rabbitmq-plugins

列表查看: rabbitmq-plugins list

​ e* 表示启用的

建议:在开发阶段我们可以开启消息追踪插件,在实际生产环境不建议建议开启,除非是非常特殊的业务场景

关闭插件:rabbitmq-plugins disable rabbitmq_tracing

关闭Firehose:rabbitmqctl trace_off

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值