RabbitMQ 学习笔记

RabbitMQ (狂神说学习笔记)

1、消息队列预科(了解一下)

消息队列协议

所谓协议是指

  1. 计算机底层操作系统和应用程序通讯时共同遵守的一组约定,只有遵循共同的约定和规范,系统和底层操作系统之间才能相互交流
  2. 和一般的网络应用程序的不同它主要负责数据的接受和传递,所以性能比较的高
  3. 协议对数据格式和计算机之间交换数据都必须严格遵守规范

网络协议的三要素

  1. 语法:语法是用户数据与控制信息的结构与格式,以及数据出现的顺序
  2. 语义:语义是解释控制信息每个部分的意义,它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应
  3. 时序:时序是对事件发生顺序的详细说明

面试题: 为什么消息中间件不直接使用 http协议 (先过个眼熟)

为什么,因为不合适,没有最好的,最有最合适的。

因为 http请求报文头和响应报文头是比较复杂的,包含了Cookie,数据的加密解密,窗台吗,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速
大部分情况下 http大部分都是短链接,在实际的交互过程中,一个请求到响应都很有可能会中断,中断以后就不会执行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取信息的过程,出现问题和故障要对数据或消息执行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行

总结来说:http协议不是专业做消息中间件的,有很多东西不符合消息中间件所需要的。消息中间件进行的是消息和数据的传输,而http协议则是进行一些网络请求,里面包含着一些消息中间件不需要的东西,比如Cookie,请求响应等。

常用的一些协议

1、AMQP协议

AMQP:(全称:Advanced Message Queuing Protocol)是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现由 RabbitMQ等

特性:

分布式事务支持
消息的持久化支持
高性能和高可靠的消息处理优势
RabbitMQ 学习笔记_第1张图片

2、MQTT协议

MQTT协议(Message Queueing Telemetry Transport)消息队列是 IBM开放的及时通讯协议,物联网系统架构中的重要组成部分

特点:

轻量,主要特点

传输快,不支持事务
没有持久化设计

3、OpenMessage协议

是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式信息中间件、流处理等领域的应用开发标准

特点:

  1. 结构简单
  2. 解析速度快
  3. 支持事务和持久化设计

4、Kafka协议

Kafka协议是基于 TCP/IP的二进制协议。消息内部是 通过长度来分割,由一些基本数据类型组成

特点:

  1. 结构简单
  2. 解析速度快
  3. 无事务支持
  4. 有持久化设计

另外就是扯一嘴关于持久化这个概念,一般我们通常在数据库方面听这个词比较多,大概是个什么意思呢?

就是将数据存入磁盘中,数据能够长久保存,不会断电即失。

2、消息分发策略

RabbitMQ 学习笔记_第2张图片

  • 发布订阅
    • 很经典的一种方式,一旦消息消费者订阅了消息发布者的信息,只要消息发布者发送了消息,消费者就会接收到发布者发送过来的信息。
  • 轮询分发 和 公平分发(其实公不公平按照自己的标准来评判)
    • 公平分发,就是你的服务器性能越好,接收数据速度越快,接收到的消息就越多
    • 轮询分发,就是无论你的服务器性能是怎么样的,快也好,慢也好,接收到的消息条数都是一样的
    • 再提一嘴,这在之后的work模式中就是这两种消息分发策略

3、消息的高可用和高可靠

这个就不聊了, 有兴趣的小伙伴可以查看这篇文章https://www.kuangstudy.com/zl/rabbitmq#1366029180994654209 (飞哥的原稿博客)

4、RabbitMQ的入门和安装

这里在linux下使用rpm安装包解压缩安装为例

  • 首先RabbitMQ是使用Erlang语言开发的,我们需要安装Erlang 和 RabbitMQ

  • 然后我们需要对应Erlang和RabbitMQ版本,详情见 https://www.rabbitmq.com/which-erlang.html


RabbitMQ 学习笔记_第3张图片

  • 下载Erlang
    • 下载地址 https://github.com/rabbitmq/erlang-rpm
    • centos8 使用Erlang24 + el8
    • centos7 使用Erlang23 + el7
    • 其余操作系统看上面那个网址的readme部分
  • 下载RabbitMQ
    • 下载地址 https://github.com/rabbitmq/rabbitmq-server/releases
    • 注意对应你的Erlange版本选择对应的RabbitMQ版本,版本对印表在上面有贴图显示
  • 剩下的就是在linux下解压缩rpm包了

在看视频的时候,发现许多学习的小伙伴说卡在这里了,原因是没有找准好版本。这里给大家一个建议,在安装一个应用的时候,可以先百度搜索一下会出现什么坑,大致了解一下后再去安装会顺很多。

另外建议观看视频,狂神说里面搜索rabbitmq,是飞哥讲的,还不错,关于微服务的部分还没有看完,有时间继续。

5、RabbitMQ 的 web管理界面以及授权操作

类似于记单词中人对图片的记忆会比单纯的文字强很多。我们很有必要先使用web可视化界面的方式先熟悉一下,然后再使用代码实现,学习起来会容易理解一些。

RabbitMQ 管理界面

1、默认情况下,rabbitmq是没有安装web端的客户端插件,需要安装才可以生效

在linux系统上输入该条命令

rabbitmq-plugins enable rabbitmq_management

2、安装完成之后,启动服务即可

systemctl restart rabbitmq-server	

3、浏览器访问,你的公网ip地址:15672,出现如下画面)切记,要在安全组和防火墙开放对应端口
RabbitMQ 学习笔记_第4张图片

4、授权创建新用户

# 创建新用户
rabbitmqctl add_user admin admin
# 授权
rabbitmqctl set_user_tags admin administrator

6、RabbitMQ角色分类(了解一下)

下面角色的权限是层层递进的, 这些大概了解一下就好了。

none

被设置成为这个角色的用户无法登陆这个Web页面

management (相当于个人中心)

被设置成为这个角色的用户只能访问跟自己相关的一些信息,你无法访问其他用户的一些信息

  • 列出自己可以通过AMQP登入的虚拟机
  • 查看自己的虚拟机节点 virtual hosts的queues,exchanges和bindings信息
  • 查看和关闭自己的channels和connections
  • 查看有关自己的虚拟机节点virtual hosts的统计信息。包括其他用户在这个节点virtual hosts中的活动信息。

Policymaker

  • 包含management所有权限
  • 查看和创建和删除自己的virtual hosts所属的policies和parameters信息。

Monitoring

  • 包含management所有权限
  • 罗列出所有的virtual hosts,包括不能登录的virtual hosts。
  • 查看其他用户的connections和channels信息
  • 查看节点级别的数据如clustering和memory使用情况
  • 查看所有的virtual hosts的全局统计信息。

Administrator

  • 最高权限
  • 可以创建和删除virtual hosts
  • 可以查看,创建和删除users
  • 查看创建permisssions
  • 关闭所有用户的connections

7、RabbitMQ快速入门案例

消息通信机制

前5种要掌握

RabbitMQ 学习笔记_第5张图片

在work模式中有两种 (在Part2 消息的分发策略中有提及)

  • 轮询
  • 公平分发

入门案例

入门案例给学者一个快速上手的案例,建议多加理解

这小节讲述最简单的模式(Hello World),原理图如下

RabbitMQ 学习笔记_第6张图片

创建maven项目,导入依赖

编写生产者

package com.company.service.helloworld;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {

    public static void main(String[] args) {

        // 这个是自动注入的,在rabbitAutoConfiguration中可以查看到
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
           // 创建连接对象
           connection =  connectionFactory.newConnection("生产者");
           // RabbitMQ是基于channel, 说明大部分功能都是channel创建对象来实现的
           channel = connection.createChannel();
           // 通过通道, 你可以创建交换机,声明队列,使用路由key绑定交换机和队列
           String queueName = "队列1";
           // 准备消息
           String message = "Hello,world";

           /**
            * 第一个参数 : 队列的名字
            * 第二个参数 : 可持久性
            * 第三个参数 : 排他性
            * 第四个参数 : 是否自动删除
            * 第五个参数 : 创建对象携带参数
            * */
           channel.queueDeclare(queueName,false,false,true,null);


           // 发送消息
           /**
            * 第一个参数 : 交换机的名字, 简单模式使用的是默认的交换机, 直接填空字符串即可. 注意!!! 消息都会经过交换机然后再发送给队列, 没有写队列名就选择了默认队列
            * 第二个参数 : 队列的名字 / 路由key, 现在的东西都是陌生的, 不要着急,学到后面就会慢慢理解里面的含义
            * 第三个参数 : 基本属性
            * 第四个参数 : 消息的内容
            * */
           channel.basicPublish("",queueName,null,message.getBytes(StandardCharsets.UTF_8));
           System.out.println("消息发送成功");

        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 通常在finally中进行资源的释放
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:

RabbitMQ 学习笔记_第7张图片

查看web页面

RabbitMQ 学习笔记_第8张图片

编写消费者

package com.company.service.helloworld;

import com.rabbitmq.client.*;

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

public class Consumer {
    public static void main(String[] args) {
        // 所有的中间件技术都是基于tcp/ip协议基础之上构建的新型的协议规范,只不过rabbitmq遵循的是amqp协议, 是因为基本的tcp/ip协议无法满足这些需求了
        // 基于tcp/ip 你就需要两个东西 ip 端口
        
        // 1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
            // 可能会有人问,这不是消费者吗? 为什么是生产者呢? 这只是给连接对象的一个名字代号而已,你变更成为消费者也是可以的,
            // 况且生产者发送了消息过后,通道和连接都释放了。
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 消费者获取消息, 如何获取, 绑定队列,
            // 第一个参数: 队列名 / 路由key 总之能绑定到队列就好了, 到后面会发现其实队列名也是路由key的一种表现形式
            // 第二个参数: 数据接收成功回调
            // 第三个参数: 数据接收失败回调
            channel.basicConsume("队列1", new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    System.out.println("接收到的消息是" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接收失败了...");
                }
            });
            System.out.println("数据接收成功");
            // 卡着看运行结果
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:
RabbitMQ 学习笔记_第9张图片

结束程序运行(因为使用了System.in.read()),查看web页面结果
RabbitMQ 学习笔记_第10张图片

显示没有队列了,为什么???

在创建队列的时候我们设置了自动删除,当队列中没有消息的时候就会自动删除队列了。

// 第一个参数 队列的名称
// 第二个参数 持久化 服务器重启时,消息还在,设置了在web页面会有D标签, 非持久化队列也会存盘,但重启服务器的话就会丢失。
// 第三个参数 排他性 是否是一个独占队列, 一般设置为false
// 第四个参数 自动删除 随着消费者消费完消息后, 消息队列是否被删除,
// 第五个参数 参数传递 设置了在web页面中会有args标签,否则没有。
channel.queueDeclare(queueName,false,false,true,null);

理一下思路

生产者:创建连接(ConnectionFactory —> connection —> channel), 创建交换机【这里使用的默认交换机,所以没有创建了】,创建队列【queueDeclare】(因为我们的rabbitmq是空的),使用交换机往队列中发送消息【basicPublish】

消费者:创建连接(ConnectionFactory —> connection —> channel) 【代码一致】,绑定队列接收消息【basicConsume】

小节总结(一定要认真了解第一个代码,里面包含了这个技术运行的基本套路)

  • 首先,RabbitMQ是基于channel去处理请求的,而不是connect,所有重要操作都是channel去实现的,为什么要这么做???
    • 连接要经过三次握手,四次挥手,每次开启关闭的开销都很大,我们要想办法减少连接的次数,就是将短连接转换为长连接(池化技术就是一种)。
  • 然后,学习RabbitMQ你要熟知的,讲解视频的老师也会经常重复的,非常重要的点,生产者创建的所有消息都是经过交换机转发给队列的,每个队列必定会绑定一个交换机。就算如同上述简单模式没有显式的绑定交换机那样,rabbitmq会自动给这种队列绑定上一个默认的交换机。(下面给出web页面上关于默认交换机的介绍:这个交换机隐式的绑定了每个队列,路由key就是队列的名字,没有必要显式的给队列绑定默认交换机或者解绑默认交换机,同时这个交换机是不可以被删除的)
    • RabbitMQ 学习笔记_第11张图片

8、RabbitMQ的核心组成成分

RabbitMQ 学习笔记_第12张图片

(图片摘自学相伴)

里面很详细的讲述了关于消息队列进行消息通信的各个流程。

首先用户通过创立连接跟rabbitmq打交道,中间那个borker就是代理的意思,就是我们启动的rabbitmq服务。再次重复一遍,所有的消息都是通过交换机转发到队列的,每个队列必定会绑定一个交换机,交换机如何查找到不同的队列呢?通过路由key,我们已经见识了一种很普通的路由key,队列的名字。消费者也是通过创立连接跟我们的rabbitmq打交道,通过绑定队列来获取消息。这个跟第一个程序流程一致,跟后面的几种模式也是流程一致的,只是在交换机和队列的绑定路由key上面下了功夫,以满足不同场景需求。

你要知道的有

  • 不会存在没有交换机的队列,没有指定交换机的队列绑定到了默认交换机上
    • 就是生产者生产消息,首先会经过交换机,然后在进入到队列中去。
  • Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。(有些场景下我们不希望所有的消费者都收到消息,这个时候我们可以使用路由key)
  • binding : Exchange和Queue之间的虚拟连接 , 我感觉就像路由表的样子这个binding
  • 多个borker(RabbitMQ服务) 就是一个消息队列集群。
  • 虚拟主机
    • 用于集群中的隔离,就好比计算机盘符中的c盘,d盘,e盘,f盘等……

分析

通过之前的代码和这幅图,可以得出RabbitMQ消费者订阅的本质,绑定了队列。所以不同模式下的消费者代码是没有变化的。

上面的术语解释:
Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server
Connection:连接,应用程序与Broker的网络连接 TCP/IP/ 三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。

9、简单模式理解(建议下面几种模式,先看可视化界面怎么弄,然后回过来再看代码实现)

首先贴上官网的原理图
RabbitMQ 学习笔记_第13张图片

我们的入门案例就是这个,是用代码实现的。

使用web开发简单模式

  • 开发简单模式,要明白的几个点
    • 原理图上也没有画交换机,使用的是默认的交换机,即队列无需显式绑定交换机,
    • 我们要弄的是
      • 创建队列
      • 从默认交换机中将消息发送出去(相当于生产者创建了消息)
      • 消费者从队列里面消费消息
    • 实现
      • 创建队列 (看下图创建队列的参数是不是和我们代码实现的基本差不多),如下设置好后(设置了队列名),add queue

        • RabbitMQ 学习笔记_第14张图片

        • 结果演示

        • RabbitMQ 学习笔记_第15张图片

      • 从默认交换机中将消息发送出去

        • 找到默认交换机,

          • RabbitMQ 学习笔记_第16张图片
        • 点击进入交换机页面,找到发送消息的地儿

          • RabbitMQ 学习笔记_第17张图片
        • 发送消息

          • 简单模式下只需要填写Routing key 和 payload即可
            • Routing key 是什么,怎么写? 在之前,我们有说过,每个队列都会绑定一个交换机,没有显式绑定声明交换机的绑定默认交换机,并且路由key 是队列名,所以我们这里填入的是我们刚创建好的队列名
            • payload : 负荷,就是你想要发送的消息,随便你填。
            • 然后点击publish message
          • RabbitMQ 学习笔记_第18张图片
        • 消费消息

          • 看下我们刚创建的队列,Ready为1,total为1,说明我们的消息发送到了这个队列中

          • RabbitMQ 学习笔记_第19张图片

          • 如何消费,消费者通过绑定队列消费,是不是可以这么理解呢?我们直接使用队列进行消息的消费,是不是模拟实现了消费者消费消息?基于上面理论,点击队列名进入队列面板

          • 队列如何消费消息(Get Message)

          • RabbitMQ 学习笔记_第20张图片

          • 讲解一下两种ack Mode

            • RabbitMQ 学习笔记_第21张图片

            • Nack message ,就相当于预览消息,消息消费后队列中的消息数不会减少

            • RabbitMQ 学习笔记_第22张图片

            • ACK, 消费消息,消息消费后,消息消费后队列中的消息数会减少,消费几条,减少几条

            • 在这里插入图片描述

            • 设置消息消费的条数

          • 预览消息

            • RabbitMQ 学习笔记_第23张图片

            • 在这里插入图片描述

          • 消费消息

            • RabbitMQ 学习笔记_第24张图片

            • 查看队列(要过5s才会刷新),消息被消费,没有了。

            • 在这里插入图片描述

代码实现简单模式

就是我们之前快速入门中填写的

10、发布订阅者模式

这个用的比较多,在之前了解MQTT的时候,很多专栏都是描述这个发布订阅者模式,大同小异。

分析原理图

RabbitMQ 学习笔记_第25张图片

P —> 生产者

X —> 交换机

红色的块 —> 队列

C1,C2 —> 消费者

web 实现

首先对比简单模式,多了一个交换机,我们这里要创建交换机,简单模式使用的是默认交换机。

  • 创建一个交换机 切记 : 使用发布订阅模式,交换机类型要选择fanout

    • RabbitMQ 学习笔记_第26张图片

    • exchange列表中有我们创建的交换机了

    • RabbitMQ 学习笔记_第27张图片

  • 创建队列,创建队列和你使用哪个模式是没有关系的,所以创建队列一样的创建

    • RabbitMQ 学习笔记_第28张图片
  • 绑定队列和交换机

    • RabbitMQ 学习笔记_第29张图片

    • 填写队列名,点击bind,把三个队列都绑定上

    • 查看绑定信息,要在绑定信息中点击队列名能够跳转到队列界面才算绑定好了

    • RabbitMQ 学习笔记_第30张图片

  • 交换机发送消息

    • RabbitMQ 学习笔记_第31张图片
  • 队列中查看消息

    • RabbitMQ 学习笔记_第32张图片

    • 随便找个队列,查看一下消息

    • RabbitMQ 学习笔记_第33张图片

代码实现

生产者代码:

package com.company.service.fanout;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 这里填入你的主机ip
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;

        try {
            connection =  connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            String exchange = "fanout_exchange";
            String routingKey = "";
            String message = "代码实现发布订阅模式";
            // 这里要绑定交换机
            channel.basicPublish(exchange,routingKey,null,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("消息发送成功");
        } catch (IOException e) {
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:
RabbitMQ 学习笔记_第34张图片

web端查看
在这里插入图片描述

消费者代码

package com.company.service.fanout;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;


public class Consumer {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {

            // 消费者基本逻辑不变
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("xxx");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");

            final String queueName = Thread.currentThread().getName();

            Connection connection = null;
            Channel channel = null;

            try {
                connection = connectionFactory.newConnection("消费者");
                channel = connection.createChannel();
                // autoAck 为 true 相当于ack
                // autoAck 为 false 相当于nack
                channel.basicConsume(queueName,true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName + "接收到的消息是" + new String(message.getBody(), StandardCharsets.UTF_8));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String consumerTag) throws IOException {
                        System.out.println(queueName + "接收消息失败");
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } finally {
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                }

                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };


    public static void main(String[] args) {
        // 这里的ThreadName 一定要绑定自己的队列名,里面的代码使用的ThreadName绑定的队列名
        new Thread(runnable, "fanout_queue1").start();
        new Thread(runnable, "fanout_queue2").start();
        new Thread(runnable, "fanout_queue3").start();
    }
}

结果演示:
RabbitMQ 学习笔记_第35张图片

web查看
RabbitMQ 学习笔记_第36张图片

11、Routing模式 / 路由模式

首先看官方原理图
RabbitMQ 学习笔记_第37张图片

对比发布订阅模式
RabbitMQ 学习笔记_第38张图片

多了些什么,

  • 是不是x (交换机上写着type=direct),说明Routing模式下的交换机类型是direct类型的。
  • 交换机和队列之间多了一些绑定关系(rabbitmq称这些绑定关系为路由key)

web实现

再次理一下操作流程,

首先讲消费者

  • 消费者之前有说过,几种模式下的变动跟消费者无关,消费者代码无需变动

然后再讲生产者

  • 首先,生产者肯定有变化的,具体变化在哪里,上面已经总结了,对比发布订阅者模式来说,
    • 创建交换机时类型要选择direct,巩固复习一下(发布订阅者模式下的交换机要选择哪种类型,不清楚的小伙伴可以翻看之前的笔记,就在上一节)
    • 然后就是交换机和队列之间的绑定关系(发布订阅者模式下没有使用路由key,而路由模式需要设置路由key)

上图:

  • 创建交换机,注意,类型选择direct类型

    • RabbitMQ 学习笔记_第39张图片
  • 创建队列

    • 创建队列没有变动
  • 设置路由key进行交换机和队列的绑定

    • RabbitMQ 学习笔记_第40张图片

    • 三个队列都绑定后

    • RabbitMQ 学习笔记_第41张图片

  • 发送消息

    • RabbitMQ 学习笔记_第42张图片
  • 消息查看

    • 在这里插入图片描述

    • RabbitMQ 学习笔记_第43张图片

    • RabbitMQ 学习笔记_第44张图片

测试:

1、使用发布订阅模式(交换机类型为fanout)去绑定多个有路由key的队列,并发送有绑定路由的消息,结果发现还是所有的队列都收到了消息

2、使用Routing模式(交换机类型为direct)去发送没有路由key的消息,页面显示消息发送,但是没有绑定路由,查看队列,所有队列都没有收到消息。

使用代码实现Routing模式

生产者代码,

package com.company.service.routing;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {

        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 这里绑定自己的主机ip
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();
            // 现在先知道每种模式的区别, 该如何发送消息, 到之后后面有一个具体的示例讲述如何创建不同类型的交换机以及交换机和队列之间的绑定
            // 区别在这里绑定了routing key
            channel.basicPublish("direct_exchange","email",null,"使用代码实现Routing模式".getBytes(StandardCharsets.UTF_8));
            System.out.println("消息发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者代码

package com.company.service.routing;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;


public class Consumer {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {

            // 消费者基本逻辑不变
            ConnectionFactory connectionFactory = new ConnectionFactory();
            // 这里绑定自己的主机ip
            connectionFactory.setHost("xxx");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");

            final String queueName = Thread.currentThread().getName();

            Connection connection = null;
            Channel channel = null;

            try {
                connection = connectionFactory.newConnection("消费者");
                channel = connection.createChannel();
                // autoAck 为 true 相当于ack
                // autoAck 为 false 相当于nack
                channel.basicConsume(queueName,true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName + "接收到的消息是" + new String(message.getBody(), StandardCharsets.UTF_8));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String consumerTag) throws IOException {
                        System.out.println(queueName + "接收消息失败");
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } finally {
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                }

                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };


    public static void main(String[] args) {
        // 这里的ThreadName 一定要绑定自己的队列名,里面的代码使用的ThreadName绑定的队列名
        new Thread(runnable, "direct_queue1").start();
        new Thread(runnable, "direct_queue2").start();
        new Thread(runnable, "direct_queue3").start();
    }
}

结果演示:

RabbitMQ 学习笔记_第45张图片

结果分析:

因为我们首先使用了web进行routing实现的,消费者使用的nack模式,仅仅是预览,并没有删除消息。所以在代码中会全部接收。

12、Topics 模式

还是一样,看官网给出的原理图
RabbitMQ 学习笔记_第46张图片

这次对比Routing模式
RabbitMQ 学习笔记_第47张图片

有什么区别:

  • 首先交换机类型不同,topics的交换机类型是topic类型的,路由模式的交换机是direct类型的
  • 路由key不同
    • topics模式下的路由key有通配符
    • 而routing模式下的路由key没有通配符
    • 所以,你可以理解为routing 模式是进行精准匹配的,topics模式是进行模糊匹配的,了解数据库的同学就可以很快理解其中的区别。

web 实现

  • 创建交换机

    • RabbitMQ 学习笔记_第48张图片
  • 创建队列

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

    • RabbitMQ 学习笔记_第49张图片

    • RabbitMQ 学习笔记_第50张图片

    • 两种通配符

      • * , 仅能代表一个字符

      • #, 能代表0个或者多个字符

      • # 举例
        给出如下三个路由key
        (1) #.order.#
        (2) *.user.#
        (3) *.course.*
        
        com.company.order   ---  匹配1  理由: #代表0个或者多个, order前面是两个前缀,order后面是0个后缀
        order.user 			---  匹配12 理由: 1能匹配如上解释,2能匹配是因为前面*代表一个前缀,后面#代表0个后缀
        .user.				---  匹配2  理由:  .号用来分割个数,可以理解为user前后都有一个空字符串
        user				---  都不匹配 理由: *仅能代表1个,不能代表0个
        order.user.course   ---  匹配12 理由: 3号后面还要1个后缀
        order.user.course.   --- 匹配12 理由: 3号后缀满足,但是3号前缀只能有1个前缀,*仅能代表一个
        
        # 总结
        * 只能代表1个, # 能代表0个或者多个
        
        # 建议
        使用web页面多多练习
        
    • 发送消息

      • RabbitMQ 学习笔记_第51张图片
    • 接收消息

      • 在这里插入图片描述

      • RabbitMQ 学习笔记_第52张图片

测试

使用Topics 模式实现 Routing模式是可行的。就是使用Topic 类型的交换机发送精确路由给队列。队列收到了消息

代码实现

生产者:

package com.company.service.topic;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();
			// 这里切换路由key
            channel.basicPublish("topic_exchange","com.company.order",null,"代码实现topic模式".getBytes(StandardCharsets.UTF_8));
            System.out.println("消息发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者:

package com.company.service.topic;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;


public class Consumer {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {

            // 消费者基本逻辑不变
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("xxx");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");

            final String queueName = Thread.currentThread().getName();

            Connection connection = null;
            Channel channel = null;

            try {
                connection = connectionFactory.newConnection("消费者");
                channel = connection.createChannel();
                // autoAck 为 true 相当于ack
                // autoAck 为 false 相当于nack
                channel.basicConsume(queueName,true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName + "接收到的消息是" + new String(message.getBody(), StandardCharsets.UTF_8));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String consumerTag) throws IOException {
                        System.out.println(queueName + "接收消息失败");
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } finally {
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                }

                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };


    public static void main(String[] args) {
        // 这里的ThreadName 一定要绑定自己的队列名,里面的代码使用的ThreadName绑定的队列名
        new Thread(runnable, "topic_queue1").start();
        new Thread(runnable, "topic_queue2").start();
        new Thread(runnable, "topic_queue3").start();
    }
}

结果演示:
RabbitMQ 学习笔记_第53张图片

13、Headers模式

headers模式官网上面没有,其实也是切换了一种绑定交换机和路由的方式,使用header进行绑定。

web演示

  • 创建交换机(类型选择headers)

    • RabbitMQ 学习笔记_第54张图片
  • 创建队列

    • 在这里插入图片描述
  • 绑定队列和交换机 (使用参数的形式绑定)

    • RabbitMQ 学习笔记_第55张图片

    • RabbitMQ 学习笔记_第56张图片

  • 发送消息

    • RabbitMQ 学习笔记_第57张图片
  • 接收消息

    • 在这里插入图片描述
    • RabbitMQ 学习笔记_第58张图片

14、完整的示例

在这小节之前已经讲述了RabbitMQ各种模式的区别,已经代码实现发送消息该如何编写,所以小伙伴们这小节的侧重点可以是如何使用代码创建不同类型的交换机以及交换机和队列之间的绑定问题。这里给出一个案例,剩下的靠小伙伴们自己进行融汇贯通了。

生产者代码

package com.company.service.all;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;


        try {
            connection =  connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 主要都是操作channel, 然后我们敲出对应的api就好了. 比如创建交换机,敲exchange; 创建队列, 敲queue; 绑定, 敲bind. 你熟悉了web界面的话就很容易看懂里面的参数是干什么用的

            // 创建交换机 这里我们创建一个Routing模式的交换机吧
            /**
             * 参数1: 交换机的名字
             * 参数2: 交换机的类型
             * 参数3: 是否可持久化
             * 参数4: 是否自动删除
             * 参数5: 参数
             * */
            channel.exchangeDeclare("routing_exchange","direct",true,false,null);

            // 创建队列
            channel.queueDeclare("routing_queue1",true,false,false,null);
            channel.queueDeclare("routing_queue2",true,false,false,null);
            channel.queueDeclare("routing_queue3",true,false,false,null);

            // 绑定交换机和队列
            channel.queueBind("routing_queue1","routing_exchange","email");
            channel.queueBind("routing_queue2","routing_exchange","sms");
            channel.queueBind("routing_queue3","routing_exchange","email");

            // 发送消息
            channel.basicPublish("routing_exchange","email",null,"完整的例子讲解".getBytes(StandardCharsets.UTF_8));
            System.out.println("消息发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:
在这里插入图片描述

交换机创建以及绑定
RabbitMQ 学习笔记_第59张图片

队列创建以及消息的接收
在这里插入图片描述

消费者代码

package com.company.service.all;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;


public class Consumer {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {

            // 消费者基本逻辑不变
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("xxx");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");

            final String queueName = Thread.currentThread().getName();

            Connection connection = null;
            Channel channel = null;

            try {
                connection = connectionFactory.newConnection("消费者");
                channel = connection.createChannel();
                // autoAck 为 true 相当于ack
                // autoAck 为 false 相当于nack
                channel.basicConsume(queueName,true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName + "接收到的消息是" + new String(message.getBody(), StandardCharsets.UTF_8));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String consumerTag) throws IOException {
                        System.out.println(queueName + "接收消息失败");
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } finally {
                if (channel != null && channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                }

                if (connection != null && connection.isOpen()) {
                    try {
                        connection.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    };


    public static void main(String[] args) {
        // 这里的ThreadName 一定要绑定自己的队列名,里面的代码使用的ThreadName绑定的队列名
        new Thread(runnable, "routing_queue1").start();
        new Thread(runnable, "routing_queue2").start();
        new Thread(runnable, "routing_queue3").start();
    }
}

结果演示:
RabbitMQ 学习笔记_第60张图片

使用的ack自动应答,被消费者消费后消息剔除,web页面显示
在这里插入图片描述

15、work 模式

首先看官方原理图
RabbitMQ 学习笔记_第61张图片

跟简单模式进行比较,
RabbitMQ 学习笔记_第62张图片

看区别,然后总结一句话就是,work模式就是多个消费者的简单模式。

但是work模式下面有两种分配方式,之前提过的,(在第二小节,消息分发策略中提及)

  • 轮询分配
  • 公平分配

轮询分配

一个消费者一条消息,按均分配;

多个消费者的简单模式,简单模式下使用的是默认交换机,无需创建,无需显式绑定,路由key就是队列名

  • 创建队列:
    • 在这里插入图片描述

生产者代码:

package com.company.service.work.polling;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();


            for (int i = 0; i < 20; i++) {
                String message = "你好,我的朋友" + i;
                // 简单模式默认交换机设置为空字符即可
                // 队列名对应web创建的队列名
                channel.basicPublish("","polling_queue",null,message.getBytes(StandardCharsets.UTF_8));
            }

            System.out.println("消息发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:
RabbitMQ 学习笔记_第63张图片

web查看:
在这里插入图片描述

消费者1代码:

package com.company.service.work.polling;

import com.rabbitmq.client.*;

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

public class Consumer1 {
    public static void main(String[] args) {
        // 1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 消费者获取消息, 如何获取, 绑定队列,
            // 第一个参数: 队列名 / 路由key 总之能绑定到队列就好了, 到后面会发现其实队列名也是路由key的一种表现形式
            // 第二个参数: 数据接收成功回调
            // 第三个参数: 数据接收失败回调

            // 轮询模式就是设置为自动应答即可
            channel.basicConsume("polling_queue", true, new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    // 模拟不同机器的速度, 我们使用sleep()
                    try {
                        System.out.println("接收到的消息是" + new String(message.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接收失败了...");
                }
            });
            System.out.println("consumer1正在接收消息");
            // 卡着看运行结果
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者2代码

package com.company.service.work.polling;

import com.rabbitmq.client.*;

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

public class Consumer2 {
    public static void main(String[] args) {
        // 1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 消费者获取消息, 如何获取, 绑定队列,
            // 第一个参数: 队列名 / 路由key 总之能绑定到队列就好了, 到后面会发现其实队列名也是路由key的一种表现形式
            // 第二个参数: 数据接收成功回调
            // 第三个参数: 数据接收失败回调

            // 轮询模式就是设置为自动应答即可,自动应答后会将消息剔除
            channel.basicConsume("polling_queue", true, new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery message) throws IOException {
                    // 模拟不同机器的速度, 我们使用sleep()
                    try {
                        System.out.println("接收到的消息是" + new String(message.getBody(), "UTF-8"));
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接收失败了...");
                }
            });
            System.out.println("consumer2正在接收消息");
            // 卡着看运行结果
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行方式:先运行消费者,再运行生产者,否则,就不是轮询了,然后先运行的消费者将消息全部消费了,后运行的消费者没有消息消费。

结果演示:

设置了不同延时的两个消费者,但是结果还是轮询
RabbitMQ 学习笔记_第64张图片

RabbitMQ 学习笔记_第65张图片

公平分配

根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;

生产者代码

package com.company.service.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();


            for (int i = 0; i < 20; i++) {
                String message = "你好,我的朋友" + i;
                // 简单模式默认交换机设置为空字符即可
                // 队列名对应web创建的队列名
                channel.basicPublish("","polling_queue",null,message.getBytes(StandardCharsets.UTF_8));
            }

            System.out.println("消息发送成功");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者1代码

package com.company.service.work.fair;

import com.rabbitmq.client.*;

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

public class Consumer1 {
    public static void main(String[] args) {
        // 1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 消费者获取消息, 如何获取, 绑定队列,
            // 第一个参数: 队列名 / 路由key 总之能绑定到队列就好了, 到后面会发现其实队列名也是路由key的一种表现形式
            // 第二个参数: 数据接收成功回调
            // 第三个参数: 数据接收失败回调

            // 公平分发要用到 指标要定义出来, qos = 1 就是从服务器中一次拉取多少条消息过来。想要设置公平分发就要设置这个
            // 在非自动确认消息的前提下, 如果一定数目的消息未被确认前,不进行消费新的消息
            Channel finalChannel = channel;

            finalChannel.basicQos(1);
            // 公平分配设置autoAck为false
            finalChannel.basicConsume("polling_queue", false, new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery delivery) throws IOException {
                    // 模拟不同机器的速度, 我们使用sleep()
                    try {
                        System.out.println("接收到的消息是" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        // 设置好手动应答后
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接收失败了...");
                }
            });
            System.out.println("consumer1正在接收消息");
            // 卡着看运行结果
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

消费者2代码

package com.company.service.work.fair;

import com.rabbitmq.client.*;

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

public class Consumer2 {
    public static void main(String[] args) {
        // 1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 配置连接工厂信息, 就是以账号amdin,密码admin去访问RabbitMQ服务
        connectionFactory.setHost("xxx");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");


        Connection connection = null;
        Channel channel = null;

        try {
            connection = connectionFactory.newConnection("生产者");
            channel = connection.createChannel();

            // 消费者获取消息, 如何获取, 绑定队列,
            // 第一个参数: 队列名 / 路由key 总之能绑定到队列就好了, 到后面会发现其实队列名也是路由key的一种表现形式
            // 第二个参数: 数据接收成功回调
            // 第三个参数: 数据接收失败回调
            Channel finalChannel = channel;


            finalChannel.basicQos(1);
            // 公平模式设置autoAck为false
            finalChannel.basicConsume("polling_queue", false, new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery delivery) throws IOException {
                    // 模拟不同机器的速度, 我们使用sleep()
                    try {
                        System.out.println("接收到的消息是" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                    System.out.println("接收失败了...");
                }
            });
            System.out.println("consumer2正在接收消息");
            // 卡着看运行结果
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if(channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }

            if(connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果演示:

延时低的能接受更多的消息
RabbitMQ 学习笔记_第66张图片RabbitMQ 学习笔记_第67张图片

16、RabbitMQ几种模式的总结

总结一下几种工作模式

1、简单模式

生产者:使用默认交换机向队列输入信息

消费者:所有工作模式的消费者都是一样的,指定队列进行消费就好了(下面就是说了)

2、工作模式

轮询模式:按均分配, 多个消费者的简单模式,设置自动应答为true

公平分发:能者多劳,修改自动应答为false和Qos和设置手动应答

3、发布订阅模式

使用fanout交换机向队列输入信息

4、路由模式

使用direct交换机并按照路由key向指定队列输入信息

5、Topics模式

使用topic交换机并按照模糊路由key向指定队列输入信息,可以实现路由模式(路由模式就是精确的路由key相较于topics模式)

6、header模式

使用header交换机并设置header参数向指定队列输入信息。

17、springboot集成rabbitmq

1、创建一个springboot项目,勾选rabbitmq的服务
RabbitMQ 学习笔记_第68张图片

(当然你也要勾选web场景,这幅贴图是为了演示效果)

或者导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-amqpartifactId>
dependency>

<dependency>
    <groupId>org.springframework.amqpgroupId>
    <artifactId>spring-rabbit-testartifactId>
    <scope>testscope>
dependency>

2、查看自动注入的对象是什么,有什么需要配置的

在spring-boot-autoconfigure目录下的META-INF目录下寻找RabbitMQ的对应的自动配置类
RabbitMQ 学习笔记_第69张图片

点进去,一查看有哪些被@Bean修饰的对象,二查看绑定了xxxxproperties没有
RabbitMQ 学习笔记_第70张图片

看下RabbitProperties 为我们配置了哪些属性?哪些默认属性需要修改的,然后得出下述配置

# 配置我们的服务启动的端口 
# 使用的yml格式
server:
  port: 8081

# 配置rabbitmq服务
spring:
  rabbitmq:
    # xxxx--->你的主机ip
    host: xxxx
    username: admin
    password: admin
    virtual-host: /
    port: 5672

3、配置交换机,队列,绑定

package com.company.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRabbitConfig {

    //1、声明fanout模式交换机注册
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanout_order_exchange",true,false);
    }

    //2、声明队列
    @Bean
    public Queue smsQueue() {
        return new Queue("sms.fanout.queue",true);
    }

    //2、声明队列
    @Bean
    public Queue emailQueue() {
        return new Queue("email.fanout.queue",true);
    }

    //2、声明队列
    @Bean
    public Queue duanxinQueue() {
        return new Queue("duanxin.fanout.queue",true);
    }

    //3、 声明绑定消息
    @Bean
    public Binding smsBinding() {
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }

    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }

    @Bean
    public Binding duanxinBinding() {
        return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
    }
}

4、编写业务

@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 模拟用户下单
     * @param userid
     * @param productid
     * @param num
     */
    public void makeOrder(String userid,String productid,int num) {
        //1、根据商品id查询库存是否充足

        //2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        //3、通过MQl来完成消息的分发, 就相当于channel.basicPublish
        // 参数1: 交换机 参数2: 路由key/队列名 参数3:消息内容
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 这句话实现消息发送
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);

    }
}

18、消费者编写

使用注解绑定监听队列

package com.company.service.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

// 使用注解绑定消息队列
@RabbitListener(queues = {"sms.fanout.queue"})
// 这样项目一启动,就会开启rabbitmq的消费者功能了
@Service
public class SmsConsumer {
    // 收到信息后的成功回调
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("sms fanout--->接收到了订单信息是:->" + message);
    }
}

19、整合direct模式

配置类编写

   // ============== direct模式 ============================
    //1、声明fanout模式交换机注册
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("direct_order_exchange",true,false);
    }

    //2、声明队列
    @Bean
    public Queue smsDirectQueue() {
        return new Queue("sms.Direct.queue",true);
    }

    //2、声明队列
    @Bean
    public Queue emailDirectQueue() {
        return new Queue("email.Direct.queue",true);
    }

    //2、声明队列
    @Bean
    public Queue duanxinDirectQueue() {
        return new Queue("duanxin.Direct.queue",true);
    }

    //3、 声明绑定消息 使用with绑定路由key
    @Bean
    public Binding smsBindingDirect() {
        return BindingBuilder.bind(smsDirectQueue()).to(directExchange()).with("sms");
    }

    @Bean
    public Binding emailBindingDirect() {
        return BindingBuilder.bind(emailDirectQueue()).to(directExchange()).with("email");
    }

    @Bean
    public Binding duanxinBindingDirect() {
        return BindingBuilder.bind(duanxinDirectQueue()).to(directExchange()).with("sms");
    }


业务代码编写

相比发布订阅模式设置了一个路由

public void makeOrderDirect(String userId,String productId,int num) {
    //1、 根据商品id查询库存是否充足

    //2、 保存订单
    String orderId = UUID.randomUUID().toString();
    System.out.println("订单生成成功" + orderId);

    //3、 发送消息
    String exchange = "direct_order_exchange";
    String routingKey = "email";

    rabbitTemplate.convertAndSend(exchange,routingKey,orderId);
}

消费者

代码基本不变,就是绑定的队列名进行了更换

package com.company.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@RabbitListener(queues = {"email.Direct.queue"})
@Service
public class EmailConsumer {
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("接收到了消息" + message);
    }
}

20、整合topic模式

使用注解的方式进行交换机的创建,队列的创建,

package com.company.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

// 使用注解绑定消息队列
// 使用@QueueBinding注解进行绑定,使用@Queue进行队列的创建,使用@Exchange进行交换机的创建
// 交换机可以重复创建,它默认不执行, 但是要保障消费者绑定的队列存在
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "duanxin.topic.queue",durable = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),
        key = "*.duanxin.*"
))
@Service
public class DuanxinTopicConsumer {

    // 收到信息后的成功回调
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("duanxin Topic--->接收到了订单信息是:->" + message);
    }
}

小事故

先插个眼,我猜测着应该与springboot的发送的convertandsend有关,发送的数据要设置具体格式,

使用web界面发送的数据,使用springboot去接收的时候出现了错误。
RabbitMQ 学习笔记_第71张图片

下面使用web获取的两条数据(上面一条是使用web发送的,下面一条是使用springboot集成发送的),springboot消息发送的时候设置了properties
RabbitMQ 学习笔记_第72张图片

21、过期时间TTL

过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。

如果有死信队列中,我们过期的消息就会放到死信队列中。

有两种设置过期时间的方式

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。可以设置死信队列
  • 第二种方法是对消息进行单独设置,每条消息TTL可以不同。过期的消息无法恢复

注意 如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。

1、第一种,设置队列属性

就是设置队列的属性,我们之前在创建队列的时候设置的最后一个队列可携带参数为null,这个地方可以进行对队列的属性设置

首先通过web了解一下设置ttl的key值,创建队列的最后一个参数的类型是map类型
RabbitMQ 学习笔记_第73张图片

代码实现

// ================== TTL相关配置 ===============
@Bean
public DirectExchange ttlDirectExchange() {
    return new DirectExchange("ttl_direct_exchange",true,false);
}

@Bean
public Queue ttlDirectQueue() {
    Map<String,Object> args = new HashMap<>();
    // 进行配置  第二个参数一定要是一个int类型,单位毫秒, 即过5s后消息自动过期
    args.put("x-message-ttl",5000);
    return new Queue("ttl.direct.queue",true,false,false,args);
}

// 声明绑定关系
@Bean
public Binding binding() {
    return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}

结果演示

创建的队列有TTL标签
在这里插入图片描述

查看队列详细信息
在这里插入图片描述

查看队列中的消息(5s后消息过期了)
RabbitMQ 学习笔记_第74张图片

2、设置消息属性

设置消息属性,也是之前有个消息属性我们设置的为null

public void makeOrderTTLMessage(String userId,String productId,int num) {
    //1、 根据商品id查询库存是否充足

    //2、 保存订单
    String orderId = UUID.randomUUID().toString();
    System.out.println("订单生成成功" + orderId);

    //3、 发送消息
    String exchange = "ttl_direct_exchange";
    String routingKey = "ttlmessage";

    // 给消息设置过期时间
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            //  这里是字符串类型, 单位也是毫秒
            message.getMessageProperties().setExpiration("5000");
            message.getMessageProperties().setContentEncoding("UTF-8");
            return message;
        }
    };

    rabbitTemplate.convertAndSend(exchange,routingKey,orderId,messagePostProcessor);
}

结果演示

队列没有设置为ttl过期队列,但是消息还是过期了,说明消息设置了过期时间
RabbitMQ 学习笔记_第75张图片

22、死信队列

DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

就是一个普通队列,当其他队列消息过期后,指定发送到一个队列中去,然后这个队列就被称之为死信队列。但是你要记住,消息是通过交换机发送给队列的,所以我们要配置消息过期后发送指定的交换机,然后通过绑定队列实现发送消息到死信队列中去。

在队列设置过期时间后设置死信队列。

情况2演示:消息过期

代码演示:

// =================== 死信队列配置  ========================

// 创建一个交换机,用于传送消息给死信队列
@Bean
public DirectExchange deadDirectExchange() {
    return new DirectExchange("dead_direct_exchange",true,false);
}

// 创建死信队列, 本质上就是一个普通的队列, 只不过过期的消息指定往这个队列中存储, 所以这个队列就被称之为死信队列
@Bean
public Queue deadDirectQueue() {
    return new Queue("dead_direct_queue",true,false,false,null);
}

@Bean
public Binding deadBinding() {
    return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with("dead");
}

// 创建一个交换机,用来绑定ttl队列
@Bean
public DirectExchange ttlDirectExchange() {
    return new DirectExchange("ttl_direct_exchange",true,false);
}

// 另外就是只有设置ttl的队列消息过期后才会进入到死信队列中, 而设置消息过期属性是不可以进入到死信队列中的
@Bean
public Queue ttlDirectQueue() {
    Map<String,Object> args = new HashMap<>();
    // 进行配置  第二个参数一定要是一个int类型,单位毫秒, 即过5s后消息自动过期
    // 1、进入死信队列的第一种情况,消息过期
    args.put("x-message-ttl",5000);
    // 设置死信队列,消息是通过交换机发送到队列中的,这点要切记
    args.put("x-dead-letter-exchange","dead_direct_exchange");
    // 我们的交换机是direct类型的,要绑定路由key
    args.put("x-dead-letter-routing-key","dead");

    return new Queue("ttl.direct.queue",true,false,false,args);
}

// 声明绑定关系
@Bean
public Binding binding() {
    return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}

演示结果:

ttl中的数据存入到了死信队列中
在这里插入图片描述
在这里插入图片描述

消息获取详情
RabbitMQ 学习笔记_第76张图片

情况3演示:队列达到最大长度

代码演示:

// =================== 死信队列配置  ========================

// 创建一个交换机,用于传送消息给死信队列
@Bean
public DirectExchange deadDirectExchange() {
    return new DirectExchange("dead_direct_exchange",true,false);
}

// 创建死信队列, 本质上就是一个普通的队列, 只不过过期的消息指定往这个队列中存储, 所以这个队列就被称之为死信队列
@Bean
public Queue deadDirectQueue() {
    return new Queue("dead_direct_queue",true,false,false,null);
}

@Bean
public Binding deadBinding() {
    return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with("dead");
}

// 创建一个交换机,用来绑定ttl队列
@Bean
public DirectExchange ttlDirectExchange() {
    return new DirectExchange("ttl_direct_exchange",true,false);
}

// 另外就是只有设置ttl的队列消息过期后才会进入到死信队列中, 而设置消息过期属性是不可以进入到死信队列中的
@Bean
public Queue ttlDirectQueue() {
    Map args = new HashMap<>();
    // 进行配置  第二个参数一定要是一个int类型,单位毫秒, 即过5s后消息自动过期
    // 进入死信队列中的第二种情况:超出队列长度
    args.put("x-max-length",5);

    // 设置死信队列,消息是通过交换机发送到队列中的,这点要切记
    args.put("x-dead-letter-exchange","dead_direct_exchange");
    // 我们的交换机是direct类型的,要绑定路由key
    args.put("x-dead-letter-routing-key","dead");

    return new Queue("ttl.direct.queue",true,false,false,args);
}

// 声明绑定关系
@Bean
public Binding binding() {
    return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}

数据发送

 public void makeOrderDeadMaxLength(String userId,String productId,int num) {
     //1、 根据商品id查询库存是否充足

     //2、 保存订单
     String orderId = UUID.randomUUID().toString();
     System.out.println("订单生成成功" + orderId);

     //3、 发送消息
     String exchange = "ttl_direct_exchange";
     String routingKey = "ttl";

     for (int i = 0; i < 10; i++) {
         // 设置后缀, 让输出到死信队列中的数据更加明显
         char suffix = 'a';
         suffix += i;
         rabbitTemplate.convertAndSend(exchange,routingKey,orderId + suffix);
     }
 }

结果演示:(小伙伴们可以猜一下是哪几条数据进入了死信队列)
在这里插入图片描述
在这里插入图片描述

答案揭晓:是前5条,可以看下面的结论,也可以根据队列先进先出的结构特性进行猜测。

进入死信队列的数据(下面的交换机可以证明是发送给的死信队列)
RabbitMQ 学习笔记_第77张图片

23、内存磁盘监控

参考帮助文档:https://www.rabbitmq.com/configure.html

web界面数据查看
在这里插入图片描述

1、内存监控

当内存使用超过配置的阈值或者磁盘空间剩余空间对于配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。

命令行的方式 (2者选其一)

第一种是选择使用比率进行(默认0.4)

第二种是选择设置具体的内存大小

rabbitmqctl set_vm_memory_high_watermark
rabbitmqctl set_vm_memory_high_watermark absolute 50MB

直接修改配置文件

当前配置文件:/etc/rabbitmq/rabbitmq.conf

#默认
#vm_memory_high_watermark.relative = 0.4
# 使用relative相对值进行设置fraction,建议取值在04~0.7之间,不建议超过0.7.
vm_memory_high_watermark.relative = 0.6
# 使用absolute的绝对值的方式,但是是KB,MB,GB对应的命令如下
vm_memory_high_watermark.absolute = 2GB
2、内存换页

在某个Broker节点及内存阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。

默认情况下,内存到达的阈值是50%时就会换页处理。
也就是说,在默认情况下该内存的阈值是0.4的情况下,当内存超过0.4*0.5=0.2时,会进行换页动作。

比如有1000MB内存,当内存的使用率达到了400MB,已经达到了极限,但是因为配置的换页内存0.5,这个时候会在达到极限400mb之前,会把内存中的200MB进行转移到磁盘中。从而达到稳健的运行。

可以通过设置 vm_memory_high_watermark_paging_ratio 来进行调整

vm_memory_high_watermark.relative = 0.4vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值,最好小于等于0.7)

为什么设置小于1,以为你如果你设置为1的阈值。内存都已经达到了极限了。你在去换页意义不是很大了。

3、磁盘监控

当磁盘的剩余空间低于确定的阈值时,RabbitMQ同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器崩溃。

默认情况下:磁盘预警为50MB的时候会进行预警。表示当前磁盘空间低于50MB的时候会阻塞生产者并且停止内存消息换页到磁盘的过程。
这个阈值可以减小,但是不能完全的消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间的检查空隙内,第一次检查是:60MB ,第二检查可能就是1MB,就会出现警告。

命令行模式修改

rabbitmqctl set_disk_free_limit  <disk_limit>
rabbitmqctl set_disk_free_limit memory_limit  <fraction>
disk_limit:固定单位 KB MB GB
fraction :是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)

修改配置文件

disk_free_limit.relative = 3.0
disk_free_limit.absolute = 50mb

集群部分

剩下再补充

你可能感兴趣的:(RabbitMQ,rabbitmq,学习)