RabbitMQ3.8.9+Springboot2.X实战

一. 介绍

二. Centos7.X rabbitmq骨架搭建

1.docker部署

#依次运行以下命令添加yum源
yum update
yum install epel-release -y
yum clean all
yum list

#安装并运行Docker。
yum install docker-io -y
systemctl start docker

#检查安装结果。
docker info

#启动使用Docker
systemctl start docker #运行Docker守护进程
systemctl stop docker #停止Docker守护进程
systemctl restart docker #重启Docker守护进程

#修改镜像仓库
vim /etc/docker/daemon.json
#改为下面内容,然后重启docker
{
“debug”:true,“experimental”:true,
“registry-mirrors”:[“https://xxx.mirror.aliyuncs.com”,“https://hub-mirror.c.163.com”,“https://docker.mirrors.ustc.edu.cn”]
}

#查看信息
docker info

2.下载rabbitmq镜像

rabbitmq安装方法

#拉取镜像
docker pull rabbitmq:management
​
docker run -d --hostname rabbit_host1 --name xd_rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:management
​
#介绍
-d 以守护进程方式在后台运行
-p 15672:15672 management 界面管理访问端口
-p 5672:5672 amqp 访问端口
--name:指定容器名
--hostname:设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts,作为容器主机IP的别名,并且将显示在容器的bash中
​
-e 参数
  RABBITMQ_DEFAULT_USER 用户名
  RABBITMQ_DEFAULT_PASS 密码

开放相关端口:

4369 erlang 发现口
5672 client 端通信口
15672 管理界面 ui 端口
25672 server 间内部通信口(集群使用)

关闭防火墙:

停止firewall
systemctl stop firewalld.service
禁止firewall开机启动
systemctl disable firewalld.service

三.Rabbitmq模式使用

1.添加maven依赖

官方地址:https://www.rabbitmq.com/java-client.html
依赖地址:https://mvnrepository.com/artifact/com.rabbitmq/amqp-client/5.10.0

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties><dependencies>
    <dependency>
      <groupId>com.rabbitmq</groupId>
      <artifactId>amqp-client</artifactId>
      <version>5.10.0</version>
    </dependency></dependencies>

2.简单模式测试

1.生产端

package net.custompang;

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

import java.nio.charset.StandardCharsets;

/**
 * 生产者
 */
public class Send {
    private final static String QUEUE_NAME = "hello";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);
        try (   //JDK7语法 或自动关闭 connnection和channel
                //创建连接
                Connection connection = factory.newConnection();
                //创建信道
                Channel channel = connection.createChannel()) {
            /**
             * 队列名称
             * 持久化配置:mq重启后还在
             * 是否独占:只能有一个消费者监听队列;当connection关闭是否删除队列,一般是false,发布订阅是独占
             * 自动删除: 当没有消费者的时候,自动删除掉,一般是false
             * 其他参数
             *
             * 队列不存在则会自动创建,如果存在则不会覆盖,所以此时的时候需要注意属性
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello World!";
            /**
             * 参数说明:
             * 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由,
             * 路由健名称
             * 配置信息
             * 发送的消息数据:字节数组
             */
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

2.消费端

package net.custompang;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));
            }
        };
        channel.basicConsume(QUEUE_NAME,true,consumer);

//        DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
//            String message = new String(msg, "UTF-8");
//            System.out.println(" [x] Received '" + message + "'");
//        };

        //自动确认消息
        //channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

3.工作模式测试

1.生产端

package net.custompang.work.rr;

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

import java.nio.charset.StandardCharsets;

/**
 * 生产者
 */
public class Send {
    private final static String QUEUE_NAME = "work_mq_rr";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);
        try (   //JDK7语法 或自动关闭 connnection和channel
                //创建连接
                Connection connection = factory.newConnection();
                //创建信道
                Channel channel = connection.createChannel()) {
            /**
             * 队列名称
             * 持久化配置:mq重启后还在
             * 是否独占:只能有一个消费者监听队列;当connection关闭是否删除队列,一般是false,发布订阅是独占
             * 自动删除: 当没有消费者的时候,自动删除掉,一般是false
             * 其他参数
             *
             * 队列不存在则会自动创建,如果存在则不会覆盖,所以此时的时候需要注意属性
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            /**
             * 参数说明:
             * 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由,
             * 路由健名称
             * 配置信息
             * 发送的消息数据:字节数组
             */
            for(int i=0;i<10;i++){
                String message = "Hello World!"+i;
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
                System.out.println(" [x] Sent '" + message + "'");
            }

        }
    }
}

2.消费端

  • 轮训策略 rr
package net.custompang.work.rr;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String QUEUE_NAME = "work_mq_rr";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(QUEUE_NAME,false,consumer);

//        DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
//            String message = new String(msg, "UTF-8");
//            System.out.println(" [x] Received '" + message + "'");
//        };

        //自动确认消息
        //channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}
  • 公平策略
package net.custompang.work.rr;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String QUEUE_NAME = "work_mq_fair";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        /**
         * 消费者每次消费1个,消费完成再消费另一个(实现公平策略)
         */
        channel.basicQos(1);

        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(QUEUE_NAME,false,consumer);

//        DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
//            String message = new String(msg, "UTF-8");
//            System.out.println(" [x] Received '" + message + "'");
//        };

        //自动确认消息
        //channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

4.发布订阅模型(fanout)

1.生产端:

package net.custompang.pub;

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

import java.nio.charset.StandardCharsets;

/**
 * 生产者
 */
public class Send {
    private final static String EXCHANGE_NAME = "exchange_fanout";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);
        try (   //JDK7语法 或自动关闭 connnection和channel
                //创建连接
                Connection connection = factory.newConnection();
                //创建信道
                Channel channel = connection.createChannel()) {
            //绑定交换机,fanout扇形,即广播
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
            String msg = "rabbitmq发布任务";
            channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes(StandardCharsets.UTF_8));

            System.out.println("广播消息发送成功");
        }
    }
}

2.消费端:

package net.custompang.pub;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,fanout扇形,即广播
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        //获取队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列,fanout交换机不用routingkey
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(queueName,false,consumer);
    }
}

5.Routing路由模式(direct)

和发布订阅类似

  • 应用场景:日志采集,一个队列全部收集,其它队列指定收集某一种

1.生产端

package net.custompang.direct;

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

import java.nio.charset.StandardCharsets;

/**
 * 生产者
 */
public class Send {
    private final static String EXCHANGE_NAME = "exchange_direct";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);
        try (   //JDK7语法 或自动关闭 connnection和channel
                //创建连接
                Connection connection = factory.newConnection();
                //创建信道
                Channel channel = connection.createChannel()) {
            //绑定交换机,fanout扇形,即广播
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
            String error = "error日志";
            String info = "info日志";
            String debug = "debug错误日志";
            channel.basicPublish(EXCHANGE_NAME,"errorRoutingKey",null,error.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME,"infoRoutingKey",null,info.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME,"debugRoutingKey",null,debug.getBytes(StandardCharsets.UTF_8));

            System.out.println("直连消息发送成功");
        }
    }
}

2.消费端

  • 消费1
package net.custompang.direct;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,fanout扇形,即广播
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        //获取队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列,direct交换机需要指定routingkey
        channel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey");
        channel.queueBind(queueName,EXCHANGE_NAME,"debugRoutingKey");
        channel.queueBind(queueName,EXCHANGE_NAME,"infoRoutingKey");
        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(queueName,false,consumer);
    }
}
  • 消费2
package net.custompang.direct;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv2 {
    private final static String EXCHANGE_NAME = "exchange_direct";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机,fanout扇形,即广播
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        //获取队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列,direct交换机需要指定routingkey
        channel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey");

        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(queueName,false,consumer);
    }
}

6.Toipcs主体模式

和路由模式类似

1.生产端

package net.custompang.topic;

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

import java.nio.charset.StandardCharsets;

/**
 * 生产者
 */
public class Send {
    private final static String EXCHANGE_NAME = "exchange_topic";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);
        try (   //JDK7语法 或自动关闭 connnection和channel
                //创建连接
                Connection connection = factory.newConnection();
                //创建信道
                Channel channel = connection.createChannel()) {
            //绑定交换机,fanout扇形,即广播
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
            String error = "error日志";
            String info = "info日志";
            String debug = "debug错误日志";
            channel.basicPublish(EXCHANGE_NAME,"order.log.error",null,error.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME,"order.log.info",null,info.getBytes(StandardCharsets.UTF_8));
            channel.basicPublish(EXCHANGE_NAME,"product.log.debug",null,debug.getBytes(StandardCharsets.UTF_8));

            System.out.println("直连消息发送成功");
        }
    }
}

2.消费端

*:代表一个词
#:代表一个或多个词
  • 消费1:
package net.custompang.topic;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv {
    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        //获取队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列,topic交换机需要指定routingkey
        channel.queueBind(queueName,EXCHANGE_NAME,"order.log.error");
        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(queueName,false,consumer);
    }
}
  • 消费2:
package net.custompang.topic;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Recv2 {
    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("8.131.119.145");
        factory.setUsername("admin");
        factory.setPassword("password");
        factory.setVirtualHost("/dev");
        factory.setPort(5672);

        //消费者一般不增加自动关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //绑定交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        //获取队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列,topic交换机需要指定routingkey
        channel.queueBind(queueName,EXCHANGE_NAME,"*.log.*");

        //回调方法,下面两种都行
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
                System.out.println("consumerTag消息标识="+consumerTag);
                //可以获取交换机,路由健等
                System.out.println("envelope元数据="+envelope);

                System.out.println("properties配置信息="+properties);

                System.out.println("body="+new String(body,"utf-8"));

                //手工确认消息消费,不是多条确认
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume(queueName,false,consumer);
    }
}

四.Springboot2.x整合RabbitMQ3.8.9实战

1.简单实战(topics)

  • 1.添加maven依赖
		<properties>
		<java.version>11</java.version>
		</properties>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
	<!-- 代码库 -->
	<repositories>
		<repository>
			<id>maven-ali</id>
			<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
				<updatePolicy>always</updatePolicy>
				<checksumPolicy>fail</checksumPolicy>
			</snapshots>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>public</id>
			<name>aliyun nexus</name>
			<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>
  • 2.配置yml文件
#消息队列
spring:
  rabbitmq:
    host: 8.131.119.145
    port: 5672
    virtual-host: /dev
    password: password
    username: admin
  • 3.配置类
    public static final String EXCHANGE_NAME = "order_exchange";
    public static final String QUEUE_NAME = "order_queue";
    /**
     * topic交换机
     * @return
     */
    @Bean
    public Exchange orderExchange() {
        //durable是否持久化
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
        //return new TopicExchange(EXCHANGE_NAME, true, false);
    }

    /**
     * 队列
     * @return
     */
    @Bean
    public Queue orderQueue() {
        return QueueBuilder.durable(QUEUE_NAME).build();

    }

    /**
     * 交换机和队列绑定关系
     */
    @Bean
    public Binding orderBinding(Queue queue, Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("order.#").noargs();

    }
  • 4.发送端
template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new","新订单");
  • 5.消费端
@Component
@RabbitListener(queues = "order_queue")
public class OrderMQListener {
    /**
     *
     * @param body 是什么类型都可以传,如果是某个类,也可以改成指定类
     * @param message
     * @param channel
     */
    @RabbitHandler
    public void messageHandler(Order body, Message message){
        //消息的编号
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag="+deliveryTag);
        System.out.println("message="+message.toString());
        System.out.println("body="+body);
    }
}

2.可靠性投递实战

RabbitMQ消息投递路径
生产者–>交换机->队列->消费者

通过两个的点控制消息的可靠性投递
生产者到交换机
通过confirmCallback
交换机到队列
通过returnCallback

建议
开启消息确认机制以后,保证了消息的准确送达,但由于频繁的确认交互, rabbitmq 整体效率变低,吞吐量下降严重,不是非常重要的消息真心不建议用消息确认机制
在这里插入图片描述

  • 1.生产者到交换机 通过confirmCallback
#旧版,确认消息发送成功,通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调
spring.rabbitmq.publisher-confirms=true
#新版,NONE值是禁用发布确认模式,是默认值,CORRELATED值是发布消息成功到交换器后会触发回调方法
spring.rabbitmq.publisher-confirm-type: correlated
    • yml配置
#开启消息二次确认生产者到broker的交换机,默认是none
    publisher-confirm-type: correlated
    • 发送端:
	/**
	 * 处理发送者到交换机
	 */
	@Test
	void testConfirmCallback(){
		//配置发送端到broker交换机的可靠性
		template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
			/**
			 *
			 * @param correlationData 配置
			 * @param ack 交换机是否收到消息,true是成功,false是失败
			 * @param cause 失败的原因
			 */
			@Override
			public void confirm(CorrelationData correlationData, boolean ack, String cause) {
				System.out.println("ConfirmCallback===>");
				System.out.println("ConfirmCallback===>"+correlationData);
				System.out.println("ConfirmCallback===>"+ack);
				//成功时为null
				System.out.println("ConfirmCallback===>"+cause);
				if(ack){
					System.out.println("发送成功");
					//更新数据库消息的状态为成功 TODO
				}else{
					System.out.println("发送失败,记录到日志或数据库");
					//更新数据库消息的状态为失败 TODO
				}
			}
		});

		//数据库新增一个消息记录,状态是发送 TODO
		//发送消息
		template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new","新订单");
	}
  • 2.交换机到队列 通过returnCallback
    两种模式
    交换机到队列不成功,则丢弃消息(默认)
    交换机到队列不成功,返回给消息生产者,触发returnCallback
    • 配置yml
	#开启消息二次确认,交换机到队列的可靠性投递,默认是false
    publisher-returns: true
    #交换机处理消息到路由失败,则会返回给生产者
    template:
      mandatory: true
    • 生产端
/**
	 * 处理交换机到路由队列
	 */
	@Test
	void testReturnCallback(){
		template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
			/**
			 * 正常情况不会被触发
			 * @param returnedMessage
			 */
			@Override
			public void returnedMessage(ReturnedMessage returnedMessage) {
				int code = returnedMessage.getReplyCode();
				//TODO 写入数据库
				System.out.println("code="+code);
				System.out.println("returnedMessage="+returnedMessage.toString());
			}
		});
		//数据库新增一个消息记录,状态是发送 TODO
		//发送消息
		template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"oo.order.new","新订单");
	}

3.避免重复消费实战

开启手工确认

  • 配置yml
 #消息手工确认ACK,默认是自动
    listener:
      simple:
        acknowledge-mode: manual
  • 消费端
package com.example.rabbitmqdemo.mq;

import com.example.rabbitmqdemo.pojo.Order;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RabbitListener(queues = "order_queue")
public class OrderMQListener {
    /**
     *
     * @param body 是什么类型都可以传,如果是某个类,也可以改成指定类
     * @param message
     * @param channel
     */
    @RabbitHandler
    public void messageHandler(Order body, Message message, Channel channel) throws IOException {
        //消息的编号
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag="+deliveryTag);
        System.out.println("message="+message.toString());
        System.out.println("body="+body);
        try {
            //告诉broker消息已经被确认并消费成功
            channel.basicAck(deliveryTag,false);
        } catch (Exception e) {
            //告诉broker,消息拒绝被确认,让broker重新发送,如果不重新入队就会变成死信
            channel.basicNack(deliveryTag,false,true);
            //告诉broker,消息拒绝被确认,让broker重新发送,只能一个一个,如果不重新入队就会变成死信
            channel.basicReject(deliveryTag,true);
            e.printStackTrace();
        }
    }
}

4.死信队列+延时消费介绍

  • 什么是TTL
    time to live 消息存活时间
    如果消息在存活时间内未被消费,则会别清除

  • RabbitMQ支持两种ttl设置
    单独消息进行配置ttl :容易出现问题(前面的消息堵塞后面的消息,使得不能过期)
    整个队列进行配置ttl(居多)

  • 什么是rabbitmq的死信队列
    没有被及时消费的消息存放的队列

  • 什么是rabbitmq的死信交换机
    Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机。
    在这里插入图片描述

  • 消息有哪几种情况成为死信

    • 消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false
    • 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live)
    • 队列的消息长度达到极限
      结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列

RabbitMQ管控台消息TTL测试

队列过期时间使用参数,对整个队列消息统一过期

x-message-ttl
单位ms(毫秒)
消息过期时间使用参数(如果队列头部消息未过期,队列中级消息已经过期,已经还在队列里面)

expiration
单位ms(毫秒)
两者都配置的话,时间短的先触发

RabbitMQ Web控制台测试

新建死信交换机(和普通没区别)
image-20210115172212664

新建死信队列 (和普通没区别)
在这里插入图片描述

死信交换机和队列绑定
在这里插入图片描述

新建普通队列,设置过期时间、指定死信交换机
image-20210115191150841

测试:直接web控制台往product_qeueu发送消息即可
应用场景:
通过消息触发一些定时任务,比如在某一固定时间点向用户发送提醒消息
用户登录之后5分钟给用户做分类推送、用户多少天未登录给用户做召回推送;
消息生产和消费有时间窗口要求:比如在天猫电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条 延时消息。这条消息将会在 30 分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。 如支付未完成,则关闭订单。如已完成支付则忽略

5.死信队列+延时消费实战

  • 配置类
/**
 * 新商家审核通过->new_merchant_queue  ->  死信交换机 ->  lock_merchant_dead_queue
 */
    /**
     * 死信队列
     */
    public static final  String LOCK_MERCHANT_DEAD_QUEUE = "lock_merchant_dead_queue";

    /**
     * 死信交换机
     */
    public static final String LOCK_MERCHANT_DEAD_EXCHANGE = "lock_merchant_dead_exchange";

    /**
     * 死信routingkey
     */
    public static final String LOCK_MERCHANT_ROUTING_KEY = "lock_merchant_routing_key";

    /**
     * 创建死信交换机
     * @return
     */
    @Bean
    public Exchange lockMerchantExchange(){
        return new TopicExchange(LOCK_MERCHANT_DEAD_EXCHANGE,true,false);
    }

    /**
     * 创建死信队列
     * @return
     */
    @Bean
    public Queue lockMerchantDeadQueue(){
        return QueueBuilder.durable(LOCK_MERCHANT_DEAD_QUEUE).build();
    }

    /**
     * 绑定死信交换机和死信队列
     * @return
     */
    @Bean
    public Binding lockMerchantBinding(){
        return new Binding(LOCK_MERCHANT_DEAD_QUEUE,Binding.DestinationType.QUEUE,LOCK_MERCHANT_DEAD_EXCHANGE,LOCK_MERCHANT_ROUTING_KEY,null);
    }

    /**
     * 普通队列,绑定死信交换机
     */
    public static final  String NEW_MERCHANT_QUEUE = "new_merchant_queue";
    /**
     * 绑定普通交换机
     */
    public static final String NEW_MERCHANT_EXCHANGE = "new_merchant_exchange";
    /**
     * routingkey
     */
    public static final String NEW_MERCHANT_ROUTING_KEY = "new_merchant_routing_key";

    /**
     * 创建普通交换机
     * @return
     */
    @Bean
    public Exchange newMerchantExchange(){
        return new TopicExchange(NEW_MERCHANT_EXCHANGE,true,false);
    }

    /**
     * 创建普通队列绑定死信交换机
     * 修改队列需要先删除原有的队列再创建
     * @return
     */
    @Bean
    public Queue newMerchantQueue(){
        Map<String,Object> args = new HashMap<>(3);
        args.put("x-dead-letter-exchange",LOCK_MERCHANT_DEAD_EXCHANGE);
        args.put("x-dead-letter-routing-key",LOCK_MERCHANT_ROUTING_KEY);
        args.put("x-message-ttl",10000);//10s

        return QueueBuilder.durable(NEW_MERCHANT_QUEUE).withArguments(args).build();
    }

    /**
     * 绑定交换机和队列
     * @return
     */
    @Bean
    public Binding newMerchantBinding(){
        return new Binding(NEW_MERCHANT_QUEUE,Binding.DestinationType.QUEUE,NEW_MERCHANT_EXCHANGE,NEW_MERCHANT_ROUTING_KEY,null);
    }
  • 生产端
package com.example.rabbitmqdemo.controller;

import com.example.rabbitmqdemo.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RequestMapping("api/admin/merchant")
@RestController
public class MerchantController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("check")
    public Object check(){
        //修改数据库的商家账号状态
        rabbitTemplate.convertAndSend(RabbitMQConfig.NEW_MERCHANT_EXCHANGE,RabbitMQConfig.NEW_MERCHANT_ROUTING_KEY,"商家账号通过审核");
        Map<String,Object> map = new HashMap<>();
        map.put("code",0);
        map.put("msg","账号审核通过,请10秒内上传一个商品");
        return map;
    }
}

  • 消费端
package com.example.rabbitmqdemo.mq;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RabbitListener(queues = "lock_merchant_dead_queue")
public class MerchantMQListener {
    @RabbitHandler
    public void messageHandler(String body, Message message, Channel channel){
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("deliveryTag="+deliveryTag);
        System.out.println("message="+message.toString());
        System.out.println("body="+body);

        try {
            //告诉broker,消息已经被确认并成功
            channel.basicAck(deliveryTag,false);
            //告诉broker,消息拒绝确认并返回队列重复消费(可批量)
            //channel.basicNack(deliveryTag,false,true);
            //告诉broker,消息拒绝确认并返回队列重复消费
            //channel.basicReject(deliveryTag,true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值