RabbitMQ消息中间件学习

RabbitMQ消息中间件学习

一、主流消息中间件介绍

1、ActiveMQ

ActiveMQ是Apache产品的最流行能力强劲的开源消息总线。并且它是一个完全支持JMS规范的消息中间件。
具有丰富的API,多种集群构建模式使得ActiveMQ成为业界老牌消息中间件,在小型企业中广泛应用
MQ的衡量指标:服务性能,数据存储,集群架构

1.1、ActiveMQ的集群架构模式

Master-Slave模式 、NetWork模式
在这里插入图片描述

2、Kafka

Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前属于Apache顶级项目。Kafak的主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,其一开始的目的就是就是用于日志收集。0.8版本开始支持复制,不支持事物,对消息的重复,丢失,错误没有严格的要求,适合阐释大量数据的互联网服务的数据收集业务。

2.1 Kafka的集群模式

在这里插入图片描述

3、RocketMQ

RocketMQ是阿里开源的消息中间件,目前也已经孵化为Apache顶级项目,他是纯java开发,具有高吞吐量,高可用性,适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafak,他对消息的可靠性传输及事物性做了优化,目前在阿里集团被广泛应用于交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。

RocketMQ集群模式

在这里插入图片描述

4、RabbitMQ

RabbitMQ是使用Erlang语言开发的开源消息队列系统, 基于AMQP协议来实现。AMQP的主要特征是面向消息,队列,路由(包括点对点和发布/订阅)、可靠性,安全。AMQP协议更多应用在企业系统内,对数据一致性,稳定性,和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

4.1 RabbitMQ的集群架构

镜像集群模式
在这里插入图片描述

二、RabbitMQ的核心概念

1、为什么互联网大厂使用RabbitMQ?

开源 性能优秀 稳定性搞
提供可靠性消息投递模式(Confirm)、返回模式(Return)
可以SpringAMQP完美整合,API丰富
集群模式丰富, 表达式配置,HA模式,镜像队列模式
保证数据不丢失的情况下做到高可用性,高可靠性

2、RabbitMQ高性能的原因

使用Erlang语言,Erlang语言最初是在交换机领域使用的架构模式,这样就使得RabbitMQ在Broker之间进行数据交互的性能是非常优秀的。Erlang语言有着和原生Socket一样的延迟

3、什么是AMQP高级消息队列协议

AMQP全称:Advance Message Queue Protocol
AMQP是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。

AMQP协议模型

在这里插入图片描述

4、AMQP具体的核心概念

Server:又称Broker,接收客户端的连接,实现AMQP实体服务
Connection:连接,应用程序与Broker的网络连接
Channel:网络信道,几乎所有的操作都是在Channel中进行的,Channel是进行消息读写的通道。客户端可以建立多个Channel,每个Channel代表一个会话任务。
Message:消息,服务器和应用程序之间传递的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级,延迟等高级特性。Body就是消息体的内容。
Virtual Host:虚拟主机,用于进行逻辑隔离,最上层的消息路由。一个VirtualHost里可以有若干戈Exchange和Queue,同一个Virtual Host里面不能有相同名称的Exchange或Queue。
Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列
Queue:也成MessageQueue,消息队列,保存消息并将消息转发给消费者。
Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
Routing Key : 路由规则,虚拟机可以用来确定如何路由一个特定的消息

5、RabbitMQ的整体架构是什么样子的?

在这里插入图片描述

6、RabbitMQ的消息流转

在这里插入图片描述

三、RabbitMQ的安装和使用

1、下载

在官网上下载你需要的RabbitMQ安装包

https://www.rabbitmq.com/

erlang-18.3-1.el7.centos.x86_64.rpm
socat-1.7.3.2-5.el7.lux.x86_64.rpm
rabbitmq-server-3.6.5-1.noarch.rpm

也可以在linux系统上使用命令下载

wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm

2、安装llinux的必要依赖包

yum install 
build-essential openssl openssl-devel unixODBC unixODBC-devel 
make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz

3、安装

3.1 安装Erlang

rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

在这里插入图片描述

3.2 安装socat

安装过程有可能出现如下情况:

rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm

在这里插入图片描述
上一步安装socat出现问题 需要执行下面的命令

rpm -ivg socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps

在这里插入图片描述

3.3 安装RabbitMQ

rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

在这里插入图片描述

4、配置文件修改

修改RabbitMQ的配置文件
比如修改密码、配置等等,例如:loopback_users 中的 <<“guest”>>,只保留guest

vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app

 {loopback_users, [guest]}

5、使用RabbitMQ

5.1 启动RabbitMQ

rabbitmq-server start &

在这里插入图片描述

5.2 停止RabbitMQ

rabbitmqctl stop

5.3 RabbitMQ管理插件(可视化管理)

rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述
插件安装成功之后访问

http://192.168.139.128:15672

在这里插入图片描述
如果RabbitMQ是安装在远程机器上的,在本地打开RabbitMQ管理台使用guest用户是登录不了的。默认情况下,RabbitMQ不允许guest用于远程登录。如果需要guest用户远程登录,则需要修改配置文件。将
loopback_users后的配置置空。

 {loopback_users, []}

6 命令行与管控台-基础操作

关闭应用

rabbitmqctl stop_app

启动应用

rabbitmqctl start_app

查询节点状态

rabbitmqctl status

添加用户

rabbitmqctl add_user username password

username:用户名
password:用户密码

查询出所有用户

rabbitmqctl list_users

删除用户

rabbitmqctl delete_user username

username:用户名

清除用户权限

rabbitmqctl clear_permissions -p vhostpath username

vhostpath: 虚拟主机
username: 用户名

查询用户权限

rabbitmqctl list_user_permissions username

修改用户密码

rabbitmqctl change_password username password

设置用户权限

rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"

创建虚拟主机

rabbitmqctl add_vhost vhostpath

查询出所有虚拟主机

rabbitmqctl list_vhosts

查询虚拟主机的所有权限

rabbitmqctl list_permissions -p vhostpath

删除虚拟主机

rabbitmqctl delete_vhost vhostpath 

查看所有队列信息

rabbitmqctl list_queues

清除队列里的消息

rabbitmqctl  -p vhostpath purge_queue blue

移除所有数据

rabbitmqctl reset

查看集群状态

rabbitmqctl cluster_status

四、RabbitMQ代码实操

1、简单的生产者和消费者

消息的生产者

package com.hyc.quickstart;

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

/**
 * 消息生产者.
 *
 * @className: Producer
 * @author: hyc
 * @date: 2021-10-26 20:07
 */
public class Producer {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问的虚拟主机
        factory.setVirtualHost("/");
        // 连接RabbitMQ的用户
        factory.setUsername("guest");
        // 连接RabbitMQ用户的密码
        factory.setPassword("guest");

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 通估计channel发送消息
        for (int i = 0; i < 10; i++) {
            String body = "Hello RabbitMQ";
            channel.basicPublish("", "test001", null, body.getBytes());
        }

        // 关闭相关的链接
        channel.close();
        connection.close();

    }
}

消息的消费者

package com.hyc.quickstart;

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

/**
 * 消息消费者.
 *
 * @className: Consumer
 * @author: hyc
 * @date: 2021-10-26 20:07
 */
public class Consumer {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问的虚拟主机
        factory.setVirtualHost("/");
        // 连接RabbitMQ的用户
        factory.setUsername("guest");
        // 连接RabbitMQ用户的密码
        factory.setPassword("guest");

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 创建一个队列
        // 参数一 队列名称
        // 参数二 是否持久化
        // 参数三 是否独占
        // 参数四 是否自动删除
        // 参数五 扩展参数
        String queueName = "test001";
        channel.queueDeclare(queueName, true, false, false, null);

        // 创建消息消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 设置channel
        channel.basicConsume(queueName, true, consumer);

        while (true) {
            // 获取消息
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息: " + msg);
        }

    }
}

POM文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hyc</groupId>
    <artifactId>rabbitmq-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq-api</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.6.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、Exchange交换机详解

Exchange:接收消息,并根据路由键转发消息到所绑定的队列
在这里插入图片描述

交换机的属性

name:交换机的名称
type:交换机类型,direct、topic、fanout、headers
durability:是否持久化 true-持久化 false-不持久哈
autoDelete:当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
internal:当前Exchange是否用于RabbitMQ内部使用,默认为false
argumens:扩展参数,用于扩展AMQP协议自制定化使用

Direct Exchange

所有发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue。
注意:Direct模式可以使用RabbitMQ自带的Exchange 即default Exchange,所以不需要讲Exchange进行任何绑定(binding)操作,消息传递时,RouteKey必须完全匹配才会被队列接收,否则该消息会被抛弃。
在这里插入图片描述

Direct Exchange代码示例

消息生产者

package com.hyc.rabbitmqapi.exchange.direct;

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

/**
 * 消息生产者.
 *
 * @className: ProducerDirectExchange
 * @author: hyc
 * @date: 2021-10-26 22:32
 */
public class ProducerDirectExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // RabbitMQ服务的地址
        factory.setHost("127.0.0.1");
        // RabbitMQ服务端口
        factory.setPort(5672);
        // RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        // 访问RabbitMQ用户的密码
        factory.setPassword("guest");

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机名称
        String exchangeName = "test_direct_exchange";
        // 路由键
        String routingKey = "test.direct";

        String msg = "Hello RabbitMQ This is Direct Exchange Message";

        // 发送消息
        channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
		
		// 关闭连接
        channel.close();
        connection.close();
    }
}

消息消费者

package com.hyc.rabbitmqapi.exchange.direct;

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

/**
 * 消息消费者.
 *
 * @className: ConsumerDirectExchange
 * @author: hyc
 * @date: 2021-10-26 22:32
 */
public class ConsumerDirectExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工程
        ConnectionFactory factory = new ConnectionFactory();
        // RabbitMQ服务的地址
        factory.setHost("127.0.0.1");
        // RabbitMQ服务端口
        factory.setPort(5672);
        // RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        // 访问RabbitMQ用户的密码
        factory.setPassword("guest");
        // 是否支持自动重新连接
        factory.setAutomaticRecoveryEnabled(true);
        // 每3秒钟重连一次
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机名称
        String exchangeName = "test_direct_exchange";
        // 交换机类型
        String exchangeType = "direct";
        // 队列名称
        String queueName = "test_direct_queue";
        // 路由键
        String routingKey = "test.direct";

        // 通过channel声明一个exchange
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);

        // 通过channel声明一个queue
        channel.queueDeclare(queueName, false, false, false, null);

        // 将queue与exchange进行绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 参数 队列名称  是否自动ACK consumer
        channel.basicConsume(queueName, true, consumer);

        while (true) {
            // 获取消息
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息: " + msg);
        }
    }
}
Topic Exchange

所用发送到Topic Exchange的消息都会被转发到所有关心 RouteKey中指定Topic的queue上。
Exchange将RouteKey和某个Topic进行模糊匹配,此时队列需要绑定一个Topic。
注意:可以使用通配符进行模糊匹配
符号 # 匹配一个或多个词
符号 * 只匹配一个词
例如:“log.#” 能够匹配到 “log.info.o”’
“log.*” 只能匹配到 “log.error”
在这里插入图片描述
Topic Exchange代码示例
生产者

package com.hyc.rabbitmqapi.exchange.topic;

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

/**
 * 这里是类说明.
 *
 * @className: ProducerTopicExchange
 * @author: hyc
 * @date: 2021-10-28 18:42
 */
public class ProducerTopicExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        //  访问RabbitMQ的用户密码
        factory.setPassword("guest");
        // 设置自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间3秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机的名称
        String exchangeName = "test_topic_exchange";

        // 路由键
        String routingKey1 = "user.save";
        String routingKey2 = "user.update";
        String routingKey3 = "user.delete.abc";

        String msg = "Hello RabbitMQ This is Topic Exchange Msg ";
        channel.basicPublish(exchangeName, routingKey1, null, (msg + routingKey1).getBytes());
        channel.basicPublish(exchangeName, routingKey2, null, (msg + routingKey2).getBytes());
        channel.basicPublish(exchangeName, routingKey3, null, (msg + routingKey3).getBytes());

        // 关闭连接
        channel.close();
        connection.close();
    }
}

消费者

package com.hyc.rabbitmqapi.exchange.topic;

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

/**
 * 这里是类说明.
 *
 * @className: ConsumerTopicExchange
 * @author: hyc
 * @date: 2021-10-28 18:59
 */
public class ConsumerTopicExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        //  访问RabbitMQ的用户密码
        factory.setPassword("guest");
        // 设置自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间3秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机的名称
        String exchangeName = "test_topic_exchange";
        // 交换机的类型
        String exchangeType = "topic";
        // 队列的名称
        String queueName = "test_topic_queue";
        // 路由键
        String routingKey = "user.*";
//        String routingKey = "user.#";

        // 通过channel声明一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true ,false, false, null);
        // 通过channel声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 交换机和队列建立绑定关系
        channel.queueBind(queueName, exchangeName, routingKey);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queueName, true, consumer);

        while (true) {
            // 获取消息
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("接收到的消息: " + msg);
        }
    }
}
Fanout Exchange

不处理路由键,只需要简单的将队列绑定到交换机上
发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
该类型的交换机转发消息是最快的
在这里插入图片描述

Fanout Exchange 示例代码

生产者

package com.hyc.rabbitmqapi.exchange.fanout;

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

/**
 * 这里是类说明.
 *
 * @className: ProducerFanoutExchange
 * @author: hyc
 * @date: 2021-10-28 19:30
 */
public class ProducerFanoutExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        //  访问RabbitMQ的用户密码
        factory.setPassword("guest");
        // 设置自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间3秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机的名称
        String exchangeName = "test_fanout_exchange";

        String msg = "Hello RabbitMQ This is Fanout Msg";
        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchangeName, "", null, msg.getBytes());
        }

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

消费者

package com.hyc.rabbitmqapi.exchange.fanout;

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

/**
 * 这里是类说明.
 *
 * @className: ConsumerFanoutExchange
 * @author: hyc
 * @date: 2021-10-28 19:30
 */
public class ConsumerFanoutExchange {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ的用户
        factory.setUsername("guest");
        //  访问RabbitMQ的用户密码
        factory.setPassword("guest");
        // 设置自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间3秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机的名称
        String exchangeName = "test_fanout_exchange";
        // 交换机类型
        String exchangeType = "fanout";
        // 队列名称
        String queueName = "test_fanout_queue";
        // 路由键
        String routingKey = "";

        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, exchangeName, routingKey);

        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queueName, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("接收消息: " + msg);
        }
    }
}
自定义消息发送

生产者

package com.hyc.rabbitmqapi.message;

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

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

public class ProducerCustomMessage {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // RabbitMQ服务地址
        factory.setHost("127.0.0.1");
        // RabbitMQ服务端口
        factory.setPort(5672);
        // RabbitMQ虚拟主机
        factory.setVirtualHost("/");
        // 访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 访问RabbitMQ用户的密码
        factory.setPassword("guest");
        // 是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机名称
        String exchangeName = "properties_direct_exchange";
        String routingKey = "properties_key";

        Map<String, Object> header = new HashMap<>();
        header.put("hello", "world");
        header.put("RabbitMQ", "Message");

        // 自定义消息属性
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                .contentEncoding("utf-8")
                // 消息是否持久化 1-不持久化 2-持久化
                .deliveryMode(2)
                // 消息过期时间 单位毫秒
                .expiration("10000")
                // 自定义消息
                .headers(header)
                .build();

        // 消息内容
        String message = "Hello RabbitMQ This Customer Message";

        // 发送消息
        channel.basicPublish(exchangeName, routingKey, properties, message.getBytes());

        // 关闭连接
        channel.close();
        connection.close();

    }
}

消费者

package com.hyc.rabbitmqapi.message;

import com.rabbitmq.client.*;

import java.util.Map;

/**
 * 这里是类说明.
 *
 * @className: ConsumerCustomMessage
 * @author: hyc
 * @date: 2021-10-29 15:01
 */
public class ConsumerCustomMessage {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ的服务端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机的名称
        String exchangeName = "properties_direct_exchange";
        // 交换机的类型
        String exchangeType = "direct";
        // 队列的名称
        String queueName = "properties_message_queue";
        // 路由键
        String routingKey = "properties_key";

        // 声明一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
        // 声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 将队列与交换机通过路由键进行绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 创建消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queueName, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String body = new String(delivery.getBody());
            AMQP.BasicProperties properties = delivery.getProperties();
            Map<String, Object> headers = properties.getHeaders();
            System.out.println("接收到的消息: " + body);
            System.out.printf("headers: " + headers);
        }



    }
}

Binding 绑定

Binding是Exchange和Exchange、Queue之间的连接关系
Binding中可以包含RoutingKey或参数

Queue 消息队列

消息队列,实际存储消息
属性:
Durability:是否持久化 Durable 持久化 Transient 不持久化
Auto Delete:是否自动删除。代表当最后一个监听被移除后,该Queue会自动删除

Message 消息

服务与服务之间传递的数据
本质上就是一段数据,由Properties和Payload组成
常用属性:
delivery mode、headers(自定义属性)、content_type、content_encoding、priority

virtual host 虚拟主机

虚拟主机,用于逻辑隔离,是最上层的消息路由

五、RabbitMQ的高级特性

1、如何保证消息的100%投递成功

什么是生产端的可靠性投递

  1. 保证消息可以发送出去
  2. 保证MQ节点的成功接收
  3. 发送端收到MQ节点(Broker)的确认应答
  4. 完善的消息补偿机制

生产端可靠性投递(一)

BAT/TMD 互联网大厂的解决方案

  • 消息落库,对消息的状态进行打标
    在这里插入图片描述
  • 消息的延迟投递,做二次确认,回调检查

在这里插入图片描述

消费端-幂等性保障

  • 唯一id + 指纹码 机制
    唯一Id + 指纹码 机制,利用数据库主键去重
    SELECT COUNT(1) FROM T_ORDER WHERE Id = 唯一Id + 指纹码
    优点:实现简单
    缺点:高并发下有数据库写入的性能瓶颈
    解决方案:根据ID进行分库分表进行路由算法

  • 利用redis的原子性实现
    使用redis幂等性需要考虑的问题

  • 1、我们是否需要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性

  • 2、如果不进行落库,那么都存储到缓存中,如何设置定时同步的策略

Confirm 确认消息

消息的确认是指生产者投递消息后,如果Broker收到消息则会给我们消息的生产者一个应答。生产者接收应答用来确定这条消息是否正常发送到Broker,这种方式也是消息的可靠性投递的核心保障。
在这里插入图片描述

如何实现Confirm确认消息
  • 1、在channel上开启确认模式channel.confirmSelect();
  • 2、在channel上添加监听 channel.addConfrimListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送或记录日志等后续处理。
Confirm消息代码示例

消息生产者

package com.hyc.rabbitmqapi.confirm;

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

import java.io.IOException;

/**
 * confirm模式的消息.
 *
 * @className: ProducerConfirm
 * @author: hyc
 * @date: 2021-11-01 20:05
 */
public class ProducerConfirm {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务的用户密码
        factory.setPassword("guest");

        // 是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 设置消息的确认模式
        channel.confirmSelect();

        // 交换机名称
        String exchangeName = "confirm_exchange";
        // 路由键
        String routingKey = "confirm.save";

        String msg = "Hello RabbitMQ This is Confirm Msg";
        channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());

        // 添加监听
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("---------------ACK--------------");
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("--------------No ACK------------");
            }
        });

    }
}

消息消费者

package com.hyc.rabbitmqapi.confirm;

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

/**
 * 这里是类说明.
 *
 * @className: ConsumerConfirm
 * @author: hyc
 * @date: 2021-11-01 20:05
 */
public class ConsumerConfirm {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的服务地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务端口
        factory.setPort(5672);
        // 设置RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务的用户密码
        factory.setPassword("guest");

        // 是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 交换机名称
        String exchangeName = "confirm_exchange";
        // 交换机类型
        String exchangeType = "topic";
        // 队列名称
        String queueName = "confirm_queue";
        // 路由键
        String routingKey = "confirm.#";

        // 通过channel声明一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
        // 通过channel声明一个队列
        channel.queueDeclare(queueName, false, false, false, null);
        // 将队列和叫环境通过路由键进行绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 创建一个消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);

        channel.basicConsume(queueName, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("接收消息: " + msg);
        }
    }
}

Return消息机制

Return Listener用于处理一些不可路由的消息
消息的生产者,通过指定一个Exchange和RoutingKey把消息发送到某一个消息队列中,然后我们的消费者监听队列进行消费处理。
在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就需要使用Return Listener。
Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端会自动删除消息

Return消息机制流程

在这里插入图片描述

Return 消息代码示例

消息生产者

package com.hyc.rabbitmqapi.returnlistener;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * 这里是类说明.
 *
 * @className: ProducerListener
 * @author: hyc
 * @date: 2021-11-02 19:20
 */
public class ProducerListener {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的地址
        factory.setHost("127.0.0.1");
        // 是指RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连的时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 声明交换机名称
        String exchangeName = "return_exchange";
        // 声明路由键
        String routingKey = "return.save";

        String msg = "Hello RabbitMQ This is Return Listener Msg";
        // mandatory 设置为 true 则会监听到无法路由到的消息
        channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());

        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("-------------return listener--------------");
                System.out.println("replyCode: " + replyCode);
                System.out.println("replyText: " + replyText);
                System.out.println("exchange: " + exchange);
                System.out.println("routingKey: " + routingKey);
                System.out.println("properties: " + properties);
                System.out.println("body: " + new String(body));
            }
        });


    }
}

消息消费者

package com.hyc.rabbitmqapi.returnlistener;

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

/**
 * 这里是类说明.
 *
 * @className: ConsumerListener
 * @author: hyc
 * @date: 2021-11-02 19:20
 */
public class ConsumerListener {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置访问RabbitMQ服务的地址
        factory.setHost("127.0.0.1");
        // 设置访问RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务的用户密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "return_exchange";
        // 定义交换机的类型 直连模式
        String exchangeType = "direct";
        // 定义路由键
        String routingKey = "return.*";
        // 定义队列名称
        String queueName = "return_queue";

        // 通过channel声明交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
        // 通过channel声明队列
        channel.queueDeclare(queueName,true, false, false, null);
        // 将队列与交换机通过路由键进行绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 创建消费之
        QueueingConsumer consumer = new QueueingConsumer(channel);

        // 消费消息
        channel.basicConsume(queueName, true, consumer);

        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            System.out.println("接收消息: " + new String(delivery.getBody()));
        }
    }
}

2、自定义消息消费者

通过继承DefaultConsumer

自定义消息消费者代码示例

消息生产者

package com.hyc.rabbitmqapi.customer;

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

/**
 * 自定义消费者的消息生产者.
 *
 * @className: ProducerCustomer
 * @author: hyc
 * @date: 2021-11-02 19:52
 */
public class ProducerCustomer {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "test_customer_exchange";
        // 定义路由键的名称
        String routingKey = "customer.key";

        String msg = "Hello RabbitMQ This is Customer Msg";

        for (int i = 0; i < 5;i ++) {
            // 发送消息
            channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());
        }

        // 关闭连接
        channel.close();
        connection.close();
    }
}

消息的消费者

package com.hyc.rabbitmqapi.customer;

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

/**
 * 自定义消费者的消息消费者.
 *
 * @className: ConsumerCustomer
 * @author: hyc
 * @date: 2021-11-02 19:52
 */
public class ConsumerCustomer {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "test_customer_exchange";
        String exchangeType = "topic";
        // 定义路由键的名称
        String routingKey = "customer.#";
        // 定义队列的名称
        String queueName = "test_customer_queue";

        // 通过channel声明交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
        // 通过channel声明队列
        channel.queueDeclare(queueName, true, false, false, null);
        // 将队列与交换机通过路由键绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 消费消息
        channel.basicConsume(queueName, true, new MyCustomerConsumer(channel));
    }
}

自定义的消费者

package com.hyc.rabbitmqapi.customer;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

/**
 * 这里是类说明.
 *
 * @className: MyCustomerConsumer
 * @author: hyc
 * @date: 2021-11-02 20:00
 */
public class MyCustomerConsumer extends DefaultConsumer {

    public MyCustomerConsumer(Channel channel) {
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("----------customer consumer------------");
        System.out.println("consumerTag: " + consumerTag);
        System.out.println("envelope: " + envelope);
        System.out.println("properties: " + properties);
        System.out.println("body: " + new String(body));
    }
}

3、消费端限流

什么是消费端限流

假设一个场景,首先我们的RabbitMQ服务器上有上万条未处理的消息,我们随便打开一个消费者都会出现巨量的消息全部推送过来,但是我们的单个消费端无法处理这么多数据,造成服务器宕机。
RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认的消息的前提下,如果一定数量的消息(基于通过consumer或者channel设置qos的值)未被确认前,不进行消费新的消息。

void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);

  • prefetchSize:0 消息的大小限制,0 表示消息大小不收限制
  • prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ACK,则该consumer将block掉,直到有消息ACK。
  • global:true/false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别还是consumer级别。

prefetchSize和global这两项,RabbitMQ没有实现,暂且不研究prefetchCount在no_ask=false的情况下,即在自动应答的情况下这两个值是不生效的

消费端限流代码示例

生产者

package com.hyc.rabbitmqapi.limit;

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

/**
 * 消费端消息限流 生产者.
 *
 * @className: ProducerLimit
 * @author: hyc
 * @date: 2021-11-05 16:05
 */
public class ProducerLimit {

    public static void main(String[] args) throws Exception {
        // 床架你连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务的用户密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间 单位毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机名称
        String exchangeName = "limit_exchange";
        // 定义路由键的名称
        String routingKey = "limit.key";

        String msg = "Hello RabbitMQ This is Limit Msg";

        for (int i = 0; i < 10; i++) {
            channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
        }

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

消费者

package com.hyc.rabbitmqapi.limit;

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

/**
 * 消费端消息限流 消费者.
 *
 * @className: ConsumerLimit
 * @author: hyc
 * @date: 2021-11-05 16:06
 */
public class ConsumerLimit {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的服务的地址
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 是指是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间 单位 毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "limit_exchange";
        // 定义交换机的类型
        String exchangeType = "topic";
        // 定义队列的名称
        String queueName = "limit_queue";
        // 定义路由键
        String routingKey = "limit.*";

        // 通过channel声明交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
        // 通过channel声明队列
        channel.queueDeclare(queueName, true, false, false, null);
        // 将队列与交换机通过路由键绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 每次只消费1条消息
        channel.basicQos(0, 1, false);

        // 消费端限流的关键 将自动ack设置为手动ack
        // 消费消息 设置为手动ack
        channel.basicConsume(queueName, false, new MyCustomerConsumer(channel));
    }
}

自定义消费者

package com.hyc.rabbitmqapi.limit;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

/**
 * 这里是类说明.
 *
 * @className: MyCustomerConsumer
 * @author: hyc
 * @date: 2021-11-05 16:31
 */
public class MyCustomerConsumer extends DefaultConsumer {

    private Channel channel;

    public MyCustomerConsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("----------customer consumer------------");
        System.out.println("consumerTag: " + consumerTag);
        System.out.println("envelope: " + envelope);
        System.out.println("properties: " + properties);
        System.out.println("body: " + new String(body));

        // ack确认收到消息 multiple: 是否批量签收消息
        channel.basicAck(envelope.getDeliveryTag(), false);
    }
}

4、消费端ACK与重回队列

消费端的手工ACK和NACK

消费端进行消费的时候,如果由于业务异常我们可以进行业务的记录然后进行补偿。
由于服务器宕机等严重问题,我们需要进行手工ACK,保障消费端消费成功。

消费端重回队列

消费端重回队列是为了处理没有消费成功的消息。把消息冲洗递给broker。
一般实际应用中,我们都会关闭消息重回队列,也就是设置为false。

重回队列代码示例

生产者

package com.hyc.rabbitmqapi.noack;

import com.rabbitmq.client.*;

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

/**
 * NoAck消息重回队列 生产者.
 *
 * @className: ProducerAckReturnQueue
 * @author: hyc
 * @date: 2021-11-05 17:09
 */
public class ProducerAckReturnQueue {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间 单位 毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机名称
        String exchangeName = "ack_return_exchange";
        // 定义路由键
        String routingKey = "ack.return";

        String msg = "Hello RabbitMQ This is Ack Or NoAck Return Queue Msg";

        for (int i = 0; i < 5; i++) {
            Map<String, Object> headers = new HashMap<>();
            headers.put("num", i);

            AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
                    .contentEncoding("utf-8")
                    .deliveryMode(2)
                    .headers(headers)
                    .build();
            // 发送消息
            channel.basicPublish(exchangeName, routingKey, basicProperties, msg.getBytes());
        }
    }
}

消费者

package com.hyc.rabbitmqapi.noack;

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

/**
 * NoAck消息重回队列 消费者.
 *
 * @className: ConsumerAckReturnQueue
 * @author: hyc
 * @date: 2021-11-05 17:09
 */
public class ConsumerAckReturnQueue {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("guest");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("guest");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 设置自动重连时间 单位 毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机名称
        String exchangeName = "ack_return_exchange";
        // 定义交换机的类型
        String exchangeType = "direct";
        // 定义队列名称
        String queueName = "ack_return_queue";
        // 定义路由键
        String routingKey = "ack.return";

        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
        channel.queueDeclare(queueName, true, false, false, null);
        channel.queueBind(queueName, exchangeName, routingKey);

        // 消费消息 autoAck:false 设置自动ack为false 使用手工签收消息
        channel.basicConsume(queueName, false, new MyCustomerConsumer(channel));
    }
}

自定义消费者

package com.hyc.rabbitmqapi.noack;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

/**
 * 这里是类说明.
 *
 * @className: MyCustomerConsumer
 * @author: hyc
 * @date: 2021-11-05 16:31
 */
public class MyCustomerConsumer extends DefaultConsumer {

    private Channel channel;

    public MyCustomerConsumer(Channel channel) {
        super(channel);
        this.channel = channel;
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("----------customer consumer------------");
        int num = (int)properties.getHeaders().get("num");
        System.out.println("body: " + new String(body) + " " + num);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (num == 0) {
            // requeue 是否重回队列 true-重回队列 false-不重回队列
            channel.basicNack(envelope.getDeliveryTag(), false, true);
        } else {
            // ack确认收到消息 multiple: 是否批量签收消息
            channel.basicAck(envelope.getDeliveryTag(), false);
        }

    }
}

5、死信队列

死信队列 DLX (Dead-Letter-Exchange)。一个消息没有被消费者消费(由于某种原因)。
利用DLX,当消息在一个队列中变成死信(Dead Message)之后,它能重新被publish到另一个Exchange,这个Exchange就是DLX。
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上进而被路由到另一个队列。
可以监听这个队列中的消息然后做相应的处理,这个特性可以弥补RabbitMQ3.0以前支持的immediate参数的功能

消息变成死信的几种情况

  • 消息被拒绝(basic.reject/basic.nack)并且requeue=false
  • 消息TTL过期
  • 队列达到最大的长度

死信队列的设置

首先要设置死信队列的exchange和queue,然后尽心绑定。

  • Exchange:dlx.exchange
  • Queue:dlx.queue
  • RoutingKey:#
    然后我们正常的声明交换机和队列,然后进行绑定。只不过需要在队列上加上一个参数即可:arguments.put(“x-dead-letter-exchange”,“dlx.exchange”);

死信队列场景代码演示

场景:发一个有过期时间10秒的消息到一个队列中,然后不消费。看10秒之后消息是否被publish到了死信队列中去了吗。
生产者

package com.hyc.rabbitmqapi.dlx;

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

/**
 * 这里是类说明.
 *
 * @className: ProducerDlx
 * @author: hyc
 * @date: 2021-11-08 18:24
 */
public class ProducerDlx {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("hyc");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("hyc");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间 单位毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "dlx_exchange";
        String routingKey = "dlx.test";

        String msg = "Hello RabbitMQ This is Test DLX msg";

        // 自定义消息属性
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                .deliveryMode(2)
                // TTL 消息过期时间10秒
                .expiration("10000")
                .contentType("utf-8")
                .build();

        // 发送消息
        channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());

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

消费者

package com.hyc.rabbitmqapi.dlx;

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

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

/**
 * 这里是类说明.
 *
 * @className: ConsumerDlx
 * @author: hyc
 * @date: 2021-11-08 18:24
 */
public class ConsumerDlx {

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置RabbitMQ服务的主机
        factory.setHost("127.0.0.1");
        // 设置RabbitMQ服务的端口
        factory.setPort(5672);
        // 设置访问RabbitMQ服务的虚拟主机
        factory.setVirtualHost("/");
        // 设置访问RabbitMQ服务的用户
        factory.setUsername("hyc");
        // 设置访问RabbitMQ服务用户的密码
        factory.setPassword("hyc");

        // 设置是否自动重连
        factory.setAutomaticRecoveryEnabled(true);
        // 自动重连时间 单位毫秒
        factory.setNetworkRecoveryInterval(3000);

        // 通过连接工厂创建连接
        Connection connection = factory.newConnection();

        // 通过连接创建channel
        Channel channel = connection.createChannel();

        // 定义交换机的名称
        String exchangeName = "dlx_exchange";
        // 定义交换机的类型
        String exchangeType = "topic";
        // 定义队列的名称
        String queueName = "dlx_queue";
        // 定义路由键
        String routingKey = "dlx.#";

        // 通过channel声明一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);

        // 定义扩展参数
        Map<String, Object> arguments = new HashMap<>();
        // 在扩展参数里定义死信队列 名称为: dlx.exchange
        arguments.put("x-dead-letter-exchange", "dlx.exchange");
        // 通过channel声明一个队列 同时携带死信队列
        channel.queueDeclare(queueName, true, false, false, arguments);
        // 将交换机和队列进行绑定
        channel.queueBind(queueName, exchangeName, routingKey);

        // 通过channel声明死信队列
        channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
        channel.queueDeclare("dlx.queue", true, false, false, null);
        channel.queueBind("dlx.queue", "dlx.exchange", "#");

        channel.basicConsume(queueName, true, new MyCustomerConsumer(channel));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值