RabbitMQ 学习(一)—— 基础知识入门


前言

记录初学 RabbitMQ 的基础知识和入门案例

一、消息中间键概述

1.1 MQ 概述

MQ 全称 Messqge Queue(消息队列),是在消息的传输过程中保存消息的容器。多用与分布式系统之间进行通行。
在这里插入图片描述

1.2 MQ 的优势

1.2.1 应用程序解耦

MQ 相当于一个中间中介,生产方通过 MQ 与消费方交互,他讲应用程序进行耦合

  • 系统的耦合性越高,容错性、可维护性就越低
    在这里插入图片描述
    图片说明: 上图的设计方案,在用户下单之后,订单系统调用库存系统进行库存数据存储,然后调用支付系统付款,然后通过物流系统发送货物等等。这种实际方案就是一个高耦合的设计方案。这种情况下,如果库存系统出现故障,那么整个的调用链路都会因为这个故障而不能继续后续的操作。而且订单下单时需要依赖库存系统时,订单系统相应给客户的就是下单操作失败的响应信息,那么我么就可以说这个设计方案的容错性很低。若是此时。我们在调用以上系统之外,还需要调用其他新增的系统,那么此时我们还需要先修改订单系统的代码,在加入对新增系统的调用,出现这样的情况,我们就可以说这个设计方案的可维护性很低

  • 使用MQ使得应用间解耦,提升容错性和可维护性。
    在这里插入图片描述
    图片说明: 再加入 MQ 之后,用户的下单操作,订单系统将下单消息放到 MQ 中,后续的库存系统、支付系统等等,都在 MQ 中消费这种订单信息,进行各自系统的业务操作。订单系统不在对其他系统进行直接调用,因此就降低了系统之间的耦合性。并且,某一系统节点的故障并不会影响后续其他系统的正常工作,如库存系统出现故障时,并不会影响支付和物流系统对订单信息的消费,当订单系统将订单信息成功写入 MQ 之后,就可以向客户响应下单操作成功的信息,等库存系统恢复正常之后在对订单信息进行消费,提高了系统的容错性。 需要加入新的系统调用时,只需要消费 MQ 中的信息就能完成新系统的集成,新的系统直接消费 MQ 中的信息,然后执行自己的业务,并不需要对订单系统进行任何的改动,提高了系统的可维护性。

1.2.2 任务异步处理

我们以下单操作为例:请添加图片描述
图片说明: 用户的下单操作之后,订单系统将数据写入数据库,然后一次调用库存、支付和物流系统。这些操作需要的时间就是 20+300+300+300=920ms;也就是说,一次下单操作正常情况下,需要经理 920ms 之后客户才会收到下单结果的响应信息。

计入MQ之后:
请添加图片描述
说明: 客户通过订单系统进行下单操作,只需要将数据写入数据库,并将订单信息写入 MQ 队列中,而在写入成功之后,无需再等库存、支付和物流系统的执行结果。就可以直接返回下单操作的响应结果,这些操作耗时 20+5=25ms;

相比 920ms 和 25ms,时间缩短了 895ms,极大的提升了用户体验和系统的吞吐量( 单位时间内处理请求的数目 )。

1.2.3 削峰填谷

以订单系统为例,在下单时将订单信息写入数据库,但是数据库每秒只能支撑 1000 左右的并发量,当超过这个阈值时,系统就容易宕机。而我们的系统低峰期时并发在 100 左右,高峰期时可以达到 5000。很明显的高峰期时,数据库肯定及卡死了。请添加图片描述
在加入 MQ 之后,消息被 MQ 保存起来,然后系统可以按照自己的消费能力,比如每秒 1000 个消息,从 MQ 中获取消息写入数据库。这样一来,数据库就不会卡死了。请添加图片描述
但是,在使用了 MQ 之后,消息消费的速度是 1000,高峰期产生的数据势必会积压在 MQ 中,这样一来订单系统的高峰就被“削”掉了,叫作 “削峰”。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是维持在 1000QPS。直至积压的消息消费完,系统的低谷就被“填”平,这就叫做 “填谷”
请添加图片描述

1.3 MQ 的劣势

  • 系统可用性降低
    系统引入的外部依赖越多,其系统稳定性就越差。一旦 MQ 宕机,就会对整个业务造成影响。 如何保证 MQ 的高可用?
  • 系统复杂度增高
    MQ 的加入极大的增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。 如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
  • 一致性问题
    A系统业务处理完成,通过MQ将消息发送给B、C、D三个系统,如果,B和C处理成功,D处理失败。如何保证消息数据处理的一致性?

1.4常见的 MQ 产品

目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、 Kafka、ZeroMQ、MetaMq 等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要 结合自身需求及 MQ 产品特征,综合考虑。
在这里插入图片描述
从综合性能来看, RabbitMQ 的综合性能还是比较强的。

1.5 AMQP 和 JMS

AMQP 和 JMS 是 MQ 的两种主流的实现方式。

1.5.1 AMQP

AMQP: 高级消息队列协议(Advanced Message Queuing Protocol),是一种网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可以传递消息,遵循此协议,不受客户端、中间件产品和开发语言的限制。 2006年,AMQP 规范发布。
在这里插入图片描述

1.5.2 JMS

JMS: Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件的 API。JMS 是 JavaEE 规范的一种,类比JDBC。很多消息中间件都实现了JMS规范,例如 ActiveMQ。RabbitMQ官方没有提供JMS的实现包,但是开源社区有。

1.5.5 比较

JMSAMQP
定义统一的接口来对消息进行统一操作通过规定协议来统一数据交互的格式
先定了必须使用Java语言AMQP只是协议,不规定实现方式,因此是跨语言的
规定了两种消息模式丰富多样的消息模式

1.6 RabbitMQ

RabbitMQ 官网链接 2007年 Rabbit 公司给予AMQP标准开发的 RabbitMQ 1.0发布。采用Erlang语言开发 。Erlang 语言专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图: 在这里插入图片描述
相关概念:

  • Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker;
  • Virtual Host:出于多组用户和安全因素设计,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中 namespace 的概念。当多个不同的用户使用同一个 RabbitMQ Server 提供的服务时,可以划分出多个 Virtual Host ,每个用户在再记得 Virtual Host 创建 exchange / queue 等。
  • Connection:publish / consumer 和 broker 之间的 TCP 连接。
  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection ,那么当大量的消息需要写入或消费时,仅建立 Connection 的开销也将是巨大的,而且效率也极低。而 Channel 是在 Connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 Channel 进行通讯。AMQP method 包含了 Channel ID 帮助客户端和 Message Broker 识别 channel,所以channel之间是完全隔离的。Channel 作为轻量级的 Connection 极大的减少了操作系统建立 TCP 连接的开销。
  • Exchange:当消息(message)到达 Broker 的第一站,根据分发规则,匹配查询表中的 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。
  • Queue:消息最终被送到这里等待 consumer 取走(消费)。
  • Binding:exchange 和 queue之间的虚拟连接,Binding 中可以包含 routing key。Binding信息被保存到 exchange 中的查询表中。

RabbitMQ 提供了6种模式:简单模式、work模式、Publish/Subscribe发布与订阅模式、Routing 路由模式、Topics主题模式、RPC远程调用模式(远程调用,暂不做介绍)。
RabbitMQ 官方模式介绍
在这里插入图片描述

二、安装及配置RabbitMQ

我们在 CentOS7 的环境下安装 RabbitMQ 服务。

2.1 安装依赖

在线安装环境

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

2.2 安装 Erlang

因为 RabbitMQ 是使用 Erlang 语言开发的,所以在安装之前,需要安装 Erlang 的语言环境。

# 安装
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

ps: 安装 Erlang 可能会出现这个错误:请添加图片描述
这是由于 GBLIC 的版本太低造成的,可以通过命令查看 GBLIC 的版本:

strings /lib64/libc.so.6 | grep GLIBC

在这里插入图片描述
可以看出我的最高版本是 2.17 。所以我安装 Erlang 的时候不会出错。若是出现上面 dos 黑框里面的错误,那就是 GBLIC 的版本太低(最低要求是2.15版本)。

当版本在2.15以下时,就要升级更新版本:

  • 更新安装依赖
do yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlitedevel readline-devel tk-devel gcc make -y
  • 下载 rpm 包
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-utils-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-static-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-common-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-devel-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/glibc-headers-2.17-55.el6.x86_64.rpm &
wget http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepoel6/epel-6-x86_64/glibc-2.17-55.fc20/nscd-2.17-55.el6.x86_64.rpm &
  • 安装 rpm 包
sudo rpm -uvh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps

安装成功以后就可以继续装 Erlang 了。

2.3 安装 RabbitMQ

# 安装
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
# 安装
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

2.4 开启管理页面

# 开启管理界⾯
rabbitmq-plugins enable rabbitmq_management
# 修改默认配置信息
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
# ⽐如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest 
# {loopback_users, [<<"guest">>]}  修改为 {loopback_users, [guest]},

2.5 启动关闭

service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停⽌服务
service rabbitmq-server restart # 重启服务

设置配置文件

cd /usr/share/doc/rabbitmq-server-3.6.5/
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

2.6 配置虚拟主机和用户

2.6.1 用户角色

RabbitMQ 安装好之后,通过 http://ip地址:15672 进行访问; 我们在上面 2.4 中已经释放了 guest 用户。现在我们就可以用 guest登录(默认密码也是 guest)。
请添加图片描述
登陆成功请添加图片描述
角色说明:

  1. 超级管理员(administrator):可登录管理控制台,查看所有信息,可对用户、策略(policy)进行操作;
  2. 监控者(monitoring):了登录管理控制台,同时查看 RabbitMQ 节点相关信息(进程数、内存使用情况、磁盘使用情况等)。
  3. 策略定制者(policymaker):可登录管理控制台,同时对 policy 进行管理。但是无法查看节点的相关信息。
  4. 普通管理者(management):仅可登录管理控制台,无法看到节点信息,也无法对策略进行管理。
  5. 其他:无法登录管理控制台,通常是普通的生产者和消费者。

2.6.2 Virtual Host 配置

类似于 mysql 中的数据库,并且可以指定用户对库和表等的操作权限。RabbitMQ 也有类似的权限管理,在 RabbitMQ 中可以创建虚拟的消息服务器 VIrtual Hosts ,每个 Virtual Hosts 就是一个相对队里的 RabbitMQ 服务器(就好像一个 mysql 服务中的不同数据库),每个 Virtual Hosts 之间是相互隔离的,exchange、queue、message 不能互通。 Virtual Hosts 名称一般以 / 开头。

2.6.2.1 创建 Virtual Hosts

进入 RabbitMQ 的管理控制台,找到 Admin 功能,然后找到 Virtual Hosts ,就能看到 Add a new virtual host 的模块。输入Virtual Hosts 名称,然后确定就可以了。请添加图片描述

2.6.2.2. 设置Virtual Hosts权限

找到创建号的 Virtual Hosts,通过点击其名称进入内部:
在这里插入图片描述
进入之后,我们能在页面上看到 Permissions 模块,点开它,找出对用的用户,添加进来,就赋予了对应用户相应的权限。请添加图片描述
添加成功:
请添加图片描述

三、RabbitMQ 工作模式

要是用 RabbitMQ 就要先引入依赖,对应的坐标如下:

// 引入 RabbitMQ 依赖
<dependency>
	<groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>5.13.1</version>
</dependency>

3.1 简单模式

3.1.1 介绍

简单模式包括了一个生产者、一个消费者和一个消息队列。工作时,生产者向消息队列中投放消息,消费者从消息队列中消费消息:
在这里插入图片描述
图解:

  • P:生产者。也就是要发送消息的程序;
  • C:消费者。消息的接收者,一直等待队列中的消息的到来;
  • Queue:消息队列(红色部分)。类似邮箱,可以换从消息;生产者向其中 投递消息,消费者从中取出消息。

3.1.2 示例代码

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

    <groupId>com.kaikeba</groupId>
    <artifactId>rabbitmq-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>
3.1.2.2 生产者
package com.kaikeba.rabbitmq.simple;

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

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

/**
 * RabbitMQ 简单模式:生产者
 */
public class Producer {
    public static final String QUEUE_NAME = "queue_simple";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // RabbitMQ 主机地址(默认是 localhost)
        connectionFactory.setHost("192.168.191.130");
        // RabbitMQ 端口(默认是5672)
        connectionFactory.setPort(5672);
        // RabbitMQ 虚拟主机名称(默认是 / )
        connectionFactory.setVirtualHost("/xzk");
        // 连接用户名 (默认是 guest)
        connectionFactory.setUsername("admin");
        // 连接密码 (默认是 guest)
        connectionFactory.setPassword("admin");

        // 创建连接
        Connection connection = connectionFactory.newConnection();
        // 创建频道
        Channel channel = connection.createChannel();

        // 创建队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接:是否只能有一个 Consumer 监听消费这个队列
         * 参数4:是否在不使用的时候自动删除队列:当没有 Consumer 的时候,是否自动删除这个消息
         * 参数5:队列其它参数
         */
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        // 定义发送的信息
        String message = "我是发送的简单模式的第一条消息:" + new Random().nextInt();

        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

        System.out.println("已发送消息:" + message);
        // 关闭资源
        channel.close();
        connection.close();
    }
}

3.1.2.3 消费者
package com.kaikeba.rabbitmq.simple;

import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 简单模式:消费者
 */
public class Consumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // RabbitMQ 服务地址
        connectionFactory.setHost("192.168.191.130");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/xzk");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

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

        // 创建队列
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);

        com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
            /**
             * 接收到消息执行的回调函数
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*super.handleDelivery(consumerTag, envelope, properties, body);*/
                // 路由 key
                System.out.println("路由 key = " + envelope.getRoutingKey());
                // 交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        // 监听消息
        channel.basicConsume(Producer.QUEUE_NAME, true, consumer);
    }
}

3.1.3 执行结果

  1. 在执行完 生产者程序之后,我们就可以在 RabbitMQ 管控台上查看队列信息
    请添加图片描述

在这里插入图片描述

  1. 后启动消费者程序,就会在控制台输出,队列消息的相关信息。
    在这里插入图片描述

3.2 工作队列模式

3.2.1 模式说明

在这里插入图片描述
Work Queues 与入门程序的简单模式相比,只是多了若干个消费者(一个或多个),多个消费端共同消费同一个队列中的消息。但是这些消费端之间是竞争关系,也就是说一个消息只能被一个消费端消费。

应用场景: 对于任务过重或任务较多情况使用工作队列,可以有效的提高任务处理速度。

3.2.2 示例代码

3.2.2.1 工具类

将连接 RabbitMQ 并获取连接的公共部分代码抽取成公共类方法:

package com.kaikeba.work.utils;

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

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

/**
 * 公共方法的抽象
 */
public class RabbitMQUtils {
    // 队列名称
    public static String WORK_QUEUE_NAME = "work_queue";

    /**
     * 抽取连接 RabbitMQ 连接的公共方法
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.191.130");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/xzk");

        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");

        return connectionFactory.newConnection();
    }
}
3.2.2.2 生产者

其实生产者代码和简单模式中的类似,不同之处在于,我们做了简单的改造,让生产者向队列中添加多条消息(我的代码里面添加了30 条)

package com.kaikeba.work.producer;

import com.kaikeba.work.utils.RabbitMQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * RabbitMQ 工作模式 生产者
 */
public class Producer {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(RabbitMQUtils.WORK_QUEUE_NAME, true, false, false, null);
        // 模拟发送 20 条消息
        for(int i = 1; i <= 30; i++) {
            String msg = "RabbitMQ 工作模式 -- " + i;
            channel.basicPublish("", RabbitMQUtils.WORK_QUEUE_NAME, null, msg.getBytes());
        }

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

}
3.2.2.3 消费者

消费者代码,和简单模式中的一模一样,我们在其中打印路由、交换机、消息id和消息内容,只不过,这里要启动两个消费者(再复制一份代码运行):

package com.kaikeba.work.consumer;

import com.kaikeba.work.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 工作模式 消费者1
 */
public class Consumer01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        //一次只能接收并处理一个消息
        channel.basicQos(1);

        channel.queueDeclare(RabbitMQUtils.WORK_QUEUE_NAME, true, false, false, null);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 路由 key
                System.out.println("路由 key = " + envelope.getRoutingKey());
                // 交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                // Consumer02 中只是将下面的 Consumer01 改为 Consumer02
                System.out.println("Consumer01 接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(RabbitMQUtils.WORK_QUEUE_NAME, true, consumer);
    }
}
3.2.2.4 结果展示
  1. 队列 和 队列中的消息
    请添加图片描述
    在这里插入图片描述
  2. 消费者消费情况
    Consumer01 消费情况:
    请添加图片描述
    Consumer02 消费情况:
    在这里插入图片描述
    小结: 通过Consumer01 和 Consumer02 的控制台信息,我们可以看出,两个消费者各自消费了一半的消息;

3.3 订阅模式

3.3.1 模式图

在这里插入图片描述
相比之前的案例和模式,订阅模式的模型中,多了一个 exchange 部分,相对应的工作流程也就发生了相应的变化:

P: 生产者。也是生产发送消息的生产者,但是生产的消息是发给 exchange 交换机的;
C: 消费者。消息的接收者,一直监听队列,等待消费消息。
● **Queue: **消息队列。接收和缓存消息。
Exchange: 交换机(途中的 X )。一方面,接收生产者发送的消息;另一方面,知道如何的处理消息,比如将消息递交到给某个特别队列、递交给所有队列、或者是将消息丢弃。具体如何操作,取决于交换机( Exchange )的类型。交换机的常见类型有三种:
Fanout:广播。将消息传递给所有绑定到交换机的队列;
Direct:定向。将消息传递给指定 routing key 的队列;
Topic:通配符。把消息交个符合对应 routing pattern(路由模式)的队列。

Exchange(交换机)只负责转发消息,不具备存储消息的能力。 因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,消息就会丢失。订阅模式,因为没有指定 routing key, 所以会将消息转发给绑定到这个交换机上的所有队列,因此,又可这种模式又可以叫做广播模式

3.3.2 示例代码

3.3.2.1 工具类

复用 3.2.2.1 工具类 代码。

3.3.2.2 生产者

与 简单模式 和 工作模式 相比多了一个 交换机 的声明部分;

package com.kaikeba.rabbitmq.producer;

import com.kaikeba.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * RabbitMQ 发布订阅模式:广播模式 Fanout
 */
public class Producer {

    // 创建交换机
    public static final String EXCHANGE_FANOUT = "fanout_exchange";

    // 创建交换机绑定的两个队列
    public static final String EXCHANGE_FANOUT_QUEUE_01 = "fanout_exchange_01";
    public static final String EXCHANGE_FANOUT_QUEUE_02 = "fanout_exchange_02";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = RabbitMQUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();

        /**
         * 声明交换机
         * 参数1:交换机名称
         * 参数2:交换机类型:fanout topic direct header(不常用)
         */
        channel.exchangeDeclare(EXCHANGE_FANOUT, BuiltinExchangeType.FANOUT);

        /**
         * 声明队列
         * 参数1:队列名称
         * 参数2:是否持久化队列
         * 参数3:是否独占本次连接,只能有一个 Consumer 监听这个队列
         * 参数4:是否自动删除,在不是用的时候自动删除队列
         * 参数5:队列其他参数
         */
        channel.queueDeclare(EXCHANGE_FANOUT_QUEUE_01, true, false, false, null);
        channel.queueDeclare(EXCHANGE_FANOUT_QUEUE_02, true, false, false, null);

        // 队列绑定交换机
        /**
         *  参数1:队列名称
         *  参数2:交换机名称
         *  参数3:routing key 消息转发时匹配队列的依据
         *
         *  Fanout 是广播机制,会对绑定到交换机上的所有队列全都进行消息分发,所以我们这里可以不指定
         */
        channel.queueBind(EXCHANGE_FANOUT_QUEUE_01, EXCHANGE_FANOUT, "");
        channel.queueBind(EXCHANGE_FANOUT_QUEUE_02, EXCHANGE_FANOUT, "");

        for (int i = 0; i < 20; i++) {
            String msg = "rabbit 工作模式的发布订阅模式 广播模式 ------ " + i;
            channel.basicPublish(EXCHANGE_FANOUT, "", null, msg.getBytes());
            System.out.println("第 " + (i + 1) + "条消息发送成功");
        }

        // 关闭资源
        channel.close();
        connection.close();
    }
}
3.3.2.3 消费者

消费者也是多出一个队列绑定交换机的过程,我们还是建立两个消费者 Consumer01 和 Consumer02,两个代码一模一样,我们只放 Consumer01 的代码:

package com.kaikeba.rabbitmq.consumer;

import com.kaikeba.rabbitmq.producer.Producer;
import com.kaikeba.rabbitmq.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 工作模式 发布订阅模式 Fanout
 */
public class Consumer01 {

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 监听队列
        channel.queueDeclare(Producer.EXCHANGE_FANOUT_QUEUE_01, true, false, false, null);

        // 队列绑定交换机
        channel.queueBind(Producer.EXCHANGE_FANOUT_QUEUE_01, Producer.EXCHANGE_FANOUT, "");

        // 接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("路由 key 为:" + envelope.getRoutingKey());
                System.out.println("交换机为:" + envelope.getExchange());
                System.out.println("消息 ID 为 :" + envelope.getDeliveryTag() );
                System.out.println("消费者 1 接收的消息为:" + new String(body, "UTF-8"));
            }
        };

        // 消费消息
        channel.basicConsume(Producer.EXCHANGE_FANOUT_QUEUE_01, true, consumer);
    }

}
3.3.2.4 结果展示
  1. 生产者启动后的结果展示
  • 交换机
    在这里插入图片描述
  • 交换机上绑定的队列
    在这里插入图片描述
  • 产生的队列
    在这里插入图片描述
  1. 消费者消费情况
  • Consumer01
    在这里插入图片描述
  • Consumer02
    在这里插入图片描述

3.4 Routing 路由模式

3.4.1 模式说明

请添加图片描述
请添加图片描述
模式图说明:
P: 生产者。向 Exchange 发送消息,发送时,需指定一个 routing key;
X: Exchange(交换机)。接收生产者的消息,然后把消息递交给 与 routing key 完全匹配的队列;
C1: 消费者。其所在队列指定了需要 routing key = error 的消息;
C2: 消费者。其所在队列指定了需要 routing key 为 info、error 和 warning 的消息。

使用过程:在声明 队列 和交换机时,需要将交换机的类型指定为 Direct,将两者绑定时,需要指定 routing key,然后,生产者将携带有 routing key 信息的消息发送到交换机,交换机根据消息携带的 routing key 去匹配队列的 routing key, 匹配成功之后,就把消息递交到对应的队列中。

3.4.2 示例代码

在这里插入图片描述
我们声明两个队列,对应的 routing 可以 分别是 insert 和 update;

3.4.2.1 工具类

复用 3.2.2.1 工具类 代码。

3.4.2.2 生产者
package com.kaikeba.routing.producer;

import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * RabbitMQ 路由模式 生产者
 */
public class Producer {

    // 交换机
    public static final String ROUTING_EXCHANGE = "routing_exchange";

    // 队列
    public static final String ROUTING_QUEUE_INSERT = "routing_queue_insert";
    public static final String ROUTING_QUEUE_UPDATE = "routing_queue_update";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(ROUTING_EXCHANGE, BuiltinExchangeType.DIRECT);

        // 声明队列
        channel.queueDeclare(ROUTING_QUEUE_INSERT, true, false, false, null);
        channel.queueDeclare(ROUTING_QUEUE_UPDATE, true, false, false, null);

        // 绑定队列和交换机
        channel.queueBind(ROUTING_QUEUE_INSERT, ROUTING_EXCHANGE, "insert");
        channel.queueBind(ROUTING_QUEUE_UPDATE, ROUTING_EXCHANGE, "update");

        // 生产发送消息
        String message = "发送消息, routing key = insert";
        channel.basicPublish(ROUTING_EXCHANGE, "insert", null, message.getBytes());

        message = "send message,routing key is update";
        channel.basicPublish(ROUTING_EXCHANGE, "update", null, message.getBytes());

        channel.close();
        connection.close();
    }
}
3.4.2.3 消费者

Consumer01 消费者 routing key 是insert

package com.kaikeba.routing.consumer;

import com.kaikeba.routing.producer.Producer;
import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 路由模式 消费者01
 */
public class Consumer01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 交换机
        channel.exchangeDeclare(Producer.ROUTING_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 队列
        channel.queueDeclare(Producer.ROUTING_QUEUE_INSERT, true, false, false, null);
        // 交换机绑定队列
        channel.queueBind(Producer.ROUTING_QUEUE_INSERT, Producer.ROUTING_EXCHANGE, "insert");

        // 回调函数:设置消息处理
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(Producer.ROUTING_QUEUE_INSERT, true, consumer);
    }
}

Consumer02 的 routing key 是 update

package com.kaikeba.routing.consumer;

import com.kaikeba.routing.producer.Producer;
import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 路由模式 消费者01
 */
public class Consumer02 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 交换机
        channel.exchangeDeclare(Producer.ROUTING_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 队列
        channel.queueDeclare(Producer.ROUTING_QUEUE_UPDATE, true, false, false, null);
        // 交换机绑定队列
        channel.queueBind(Producer.ROUTING_QUEUE_UPDATE, Producer.ROUTING_EXCHANGE, "update");

        // 回调函数:处理消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者2-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(Producer.ROUTING_QUEUE_UPDATE, true, consumer);
    }
}
3.4.2.4 结果展示
  1. 生产者程序运性之后
  • 交换机(Direct 类型)
    在这里插入图片描述
  • 交换机上绑定的 队列
    在这里插入图片描述
  • 队列中的消息
    在这里插入图片描述
  1. 消费者消费情况
  • Consumer01
    在这里插入图片描述
  • Consumer02
    在这里插入图片描述

3.4.3 多个 routing key

根据开始的图片,我们要是一个队列上有多个 routing key 的时候,应该怎么做呢?就是下面这个图:
在这里插入图片描述
我们可以看到,上面的队列的 routing key 是 error,下面的队列的 routing key 是 info、error 和 warning;这个时候我们应该怎么做呢? RabbitMQ 又是怎么处理的呢?我们做了一个测试:绑定队列和交换机的时候我们绑定多个 routing key;如下,然后查看结果:


        // 绑定队列和交换机
        channel.queueBind(ROUTING_QUEUE_01, ROUTING_EXCHANGE_TEST, "error");

        // 绑定交换机和队列
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "info");
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "debug");
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "error");
3.4.3.1 生产者
package com.kaikeba.routing.producer;

import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * RabbitMQ 路由模式 生产者
 */
public class Producer {

    // 交换机
    public static final String ROUTING_EXCHANGE_TEST = "routing_exchange_test";

    // 队列
    public static final String ROUTING_QUEUE_01 = "routing_queue_01";
    public static final String ROUTING_QUEUE_02 = "routing_queue_02";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(ROUTING_EXCHANGE_TEST, BuiltinExchangeType.DIRECT);

        // 声明队列
        channel.queueDeclare(ROUTING_QUEUE_01, true, false, false, null);
        channel.queueDeclare(ROUTING_QUEUE_02, true, false, false, null);

        // 绑定队列和交换机
        channel.queueBind(ROUTING_QUEUE_01, ROUTING_EXCHANGE_TEST, "error");

        // 绑定交换机和队列
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "info");
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "debug");
        channel.queueBind(ROUTING_QUEUE_02, ROUTING_EXCHANGE_TEST, "error");


        // 生产发送消息
        String message = "发送消息, com.kaikeba.routing key = insert";
        channel.basicPublish(ROUTING_EXCHANGE_TEST, "error", null, message.getBytes());

        message = "send message,com.kaikeba.routing key is update";
        channel.basicPublish(ROUTING_EXCHANGE_TEST, "info", null, message.getBytes());

        channel.close();
        connection.close();
    }
}
3.4.3.2 消费者01
package com.kaikeba.routing.consumer;

import com.kaikeba.routing.producer.Producer;
import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 路由模式 消费者01
 */
public class Consumer01 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 交换机
        channel.exchangeDeclare(Producer.ROUTING_EXCHANGE_TEST, BuiltinExchangeType.DIRECT);
        // 队列
        channel.queueDeclare(Producer.ROUTING_QUEUE_01, true, false, false, null);
        // 交换机绑定队列
        channel.queueBind(Producer.ROUTING_QUEUE_01, Producer.ROUTING_EXCHANGE_TEST, "insert");

        // 回调函数:设置消息处理
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(Producer.ROUTING_QUEUE_01, true, consumer);
    }
}
3.4.3.2 消费者02
package com.kaikeba.routing.consumer;

import com.kaikeba.routing.producer.Producer;
import com.kaikeba.routing.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 路由模式 消费者01
 */
public class Consumer02 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 交换机
        channel.exchangeDeclare(Producer.ROUTING_EXCHANGE_TEST, BuiltinExchangeType.DIRECT);
        // 队列
        channel.queueDeclare(Producer.ROUTING_QUEUE_02, true, false, false, null);
        // 交换机绑定队列
        channel.queueBind(Producer.ROUTING_QUEUE_02, Producer.ROUTING_EXCHANGE_TEST, "update");


        // 回调函数:处理消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者2-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(Producer.ROUTING_QUEUE_02, true, consumer);
    }
}
3.4.3.3 结果展示
  1. 生产者运性结果
  • 交换机
    请添加图片描述
    我们可以看到:当一个队列有多个 routing key 的时候,RabbitMQ 是分别创建绑定关系的,不同的队列可以绑定相同的 routing key,不会冲突,也不会出错。
    在这里插入图片描述

  • 队列中的消息
    队列routing_queue_01:只收到一条消息
    请添加图片描述
    队列routing_queue_02:收到了两条消息
    在这里插入图片描述

  1. 消费者结果
  • Consumer01
    在这里插入图片描述

  • Consuemr02
    在这里插入图片描述

3.4.4 小结

● Routing 路由模式需要指定特定的路由 key ;才能将消息发送到对应的队列中;
● 对于有多个 routing key 的队列,我们需要逐次绑定,RabbitMQ 会分别建立绑定关系;
● 不同的队列可以有相同的 routing key,消息发送时,会发送到所有匹配成功的队列。

3.5 Topics 通配符模式

3.5.1 模式说明

Topics 类型与 Direct 类型相比,都是根据 routing key 将消息路由到不同的队列。只不过 Topics 模式中 Exchange 可以让队列在绑定 routing key 的时候可以使用通配符。
routing key 一般都是由一个或多个单词组成,单词之间以 “.”分隔,例如“item.insert”、“item.update.insert” 等等;

通配符规则:

● #:匹配一个或多个词;
● *:匹配一个词。

举例:
● item.#:可以匹配到 item.insert.abc 或者 item.insert;
● item.*:只能匹配到 item.insert。
在这里插入图片描述
在这里插入图片描述

图解:
红色 Queue:绑定的是 usa.# 。凡是以 “usa.”开头的 routing key 都会被匹配到;
黄色 Queue:绑定的是 #.news 。凡是以 .news结尾的 routing key 都会被匹配到。

3.5.2 示例代码

3.5.2.1 生产者
package com.kaikeba.topics.producer;

import com.kaikeba.topics.utils.RabbitMQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * RabbitMQ 通配符模式 生产者
 */
public class Producer {
    // 交换机
    public static final String TOPICS_EXCHANGE = "topics_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(TOPICS_EXCHANGE, BuiltinExchangeType.TOPIC);

        String message = "新增商品。Topics模式:routing key = item.insert";
        channel.basicPublish(TOPICS_EXCHANGE, "item.insert", null,message.getBytes());

        message = "删除商品。Topics模式:routing key = item.delete";
        channel.basicPublish(TOPICS_EXCHANGE, "item.delete", null,message.getBytes());

        message = "修改商品。Topics模式:routing key = item.update";
        channel.basicPublish(TOPICS_EXCHANGE, "item.update", null,message.getBytes());

        channel.close();
        connection.close();
    }
}
3.5.2.2 消费者
  • Consumer01
package com.kaikeba.topics.consumer;

import com.kaikeba.topics.producer.Producer;
import com.kaikeba.topics.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 通配符模式 消费者-01
 */
public class Consumer01 {
    // 队列
    public static final String TOPICS_QUEUE_ALL = "topics_queue_all";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(Producer.TOPICS_EXCHANGE, BuiltinExchangeType.TOPIC);
        // 声明队列
        channel.queueDeclare(TOPICS_QUEUE_ALL, true, false, false, null);

        // 队列交换机绑定
        channel.queueBind(TOPICS_QUEUE_ALL, Producer.TOPICS_EXCHANGE, "item.*");

        // 回调函数:设置消息处理
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(TOPICS_QUEUE_ALL, true, consumer);
    }
}
  • Consumer02
package com.kaikeba.topics.consumer;

import com.kaikeba.topics.producer.Producer;
import com.kaikeba.topics.utils.RabbitMQUtils;
import com.rabbitmq.client.*;

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

/**
 * RabbitMQ 通配符模式 消费者-02
 */
public class Consumer02 {
    // 队列
    public static final String TOPICS_QUEUE_INSERT_UPDATE = "topics_queue_insert_update";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();

        // 声明交换机
        channel.exchangeDeclare(Producer.TOPICS_EXCHANGE, BuiltinExchangeType.TOPIC);
        // 声明队列
        channel.queueDeclare(TOPICS_QUEUE_INSERT_UPDATE, true, false, false, null);

        // 队列交换机绑定
        channel.queueBind(TOPICS_QUEUE_INSERT_UPDATE, Producer.TOPICS_EXCHANGE, "item.update");
        channel.queueBind(TOPICS_QUEUE_INSERT_UPDATE, Producer.TOPICS_EXCHANGE, "item.insert");

        // 回调函数:设置消息处理
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("消费者02-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        channel.basicConsume(TOPICS_QUEUE_INSERT_UPDATE, true, consumer);
    }
}
3.5.2.3 结果展示

因为我们这次的队列声明是放在消费者里面的,所以要先启动消费者,否在生产者生产的消息就会丢失。

  1. 消费者启动之后
  • 交换机和绑定关系
    请添加图片描述

请添加图片描述

  • 队列信息
    请添加图片描述
  1. 生产者启动
  • Consumer01
    请添加图片描述

  • Consumer02
    在这里插入图片描述

3.5.3 小结

Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。

3.6 工作模式总结

  1. 简单模式
    一个消费者一个生产者,不需要交换机(实质上使用的是RabbitMQ 默认的交换机,模式是 Direct );

  2. 工作队列模式
    一个生产者,多个消费者(多个之间是竞争关系);不需要交换机实质上使用的是RabbitMQ 默认的交换机,模式是 Direct );

  3. 发布订阅模式 Publish/subscribe
    需要设置类型为 fanout 的交换机,并且交换机和队列进行绑定, 当发送消息到交换机后,交换机会将消息发送到绑定的队列 ;

  4. 路由模式 Routing
    需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列 ;

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值