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

文章目录

  • 前言
  • 一、消息中间键概述
    • 1.1 MQ 概述
    • 1.2 MQ 的优势
      • 1.2.1 应用程序解耦
      • 1.2.2 任务异步处理
      • 1.2.3 削峰填谷
    • 1.3 MQ 的劣势
    • 1.4常见的 MQ 产品
    • 1.5 AMQP 和 JMS
      • 1.5.1 AMQP
      • 1.5.2 JMS
      • 1.5.5 比较
    • 1.6 RabbitMQ
  • 二、安装及配置RabbitMQ
    • 2.1 安装依赖
    • 2.2 安装 Erlang
    • 2.3 安装 RabbitMQ
    • 2.4 开启管理页面
    • 2.5 启动关闭
    • 2.6 配置虚拟主机和用户
      • 2.6.1 用户角色
      • 2.6.2 Virtual Host 配置
        • 2.6.2.1 创建 Virtual Hosts
        • 2.6.2.2. 设置Virtual Hosts权限
  • 三、RabbitMQ 工作模式
    • 3.1 简单模式
      • 3.1.1 介绍
      • 3.1.2 示例代码
        • 3.1.2.1 pom.xml
        • 3.1.2.2 生产者
        • 3.1.2.3 消费者
      • 3.1.3 执行结果
    • 3.2 工作队列模式
      • 3.2.1 模式说明
      • 3.2.2 示例代码
        • 3.2.2.1 工具类
        • 3.2.2.2 生产者
        • 3.2.2.3 消费者
        • 3.2.2.4 结果展示
    • 3.3 订阅模式
      • 3.3.1 模式图
      • 3.3.2 示例代码
        • 3.3.2.1 工具类
        • 3.3.2.2 生产者
        • 3.3.2.3 消费者
        • 3.3.2.4 结果展示
    • 3.4 Routing 路由模式
      • 3.4.1 模式说明
      • 3.4.2 示例代码
        • 3.4.2.1 工具类
        • 3.4.2.2 生产者
        • 3.4.2.3 消费者
        • 3.4.2.4 结果展示
      • 3.4.3 多个 routing key
        • 3.4.3.1 生产者
        • 3.4.3.2 消费者01
        • 3.4.3.2 消费者02
        • 3.4.3.3 结果展示
      • 3.4.4 小结
    • 3.5 Topics 通配符模式
      • 3.5.1 模式说明
      • 3.5.2 示例代码
        • 3.5.2.1 生产者
        • 3.5.2.2 消费者
        • 3.5.2.3 结果展示
      • 3.5.3 小结
    • 3.6 工作模式总结


前言

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

一、消息中间键概述

1.1 MQ 概述

MQ 全称 Messqge Queue(消息队列),是在消息的传输过程中保存消息的容器。多用与分布式系统之间进行通行。
RabbitMQ 学习(一)—— 基础知识入门_第1张图片

1.2 MQ 的优势

1.2.1 应用程序解耦

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

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

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

1.2.2 任务异步处理

我们以下单操作为例:RabbitMQ 学习(一)—— 基础知识入门_第4张图片
图片说明: 用户的下单操作之后,订单系统将数据写入数据库,然后一次调用库存、支付和物流系统。这些操作需要的时间就是 20+300+300+300=920ms;也就是说,一次下单操作正常情况下,需要经理 920ms 之后客户才会收到下单结果的响应信息。

计入MQ之后:
RabbitMQ 学习(一)—— 基础知识入门_第5张图片
说明: 客户通过订单系统进行下单操作,只需要将数据写入数据库,并将订单信息写入 MQ 队列中,而在写入成功之后,无需再等库存、支付和物流系统的执行结果。就可以直接返回下单操作的响应结果,这些操作耗时 20+5=25ms;

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

1.2.3 削峰填谷

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

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 学习(一)—— 基础知识入门_第9张图片
从综合性能来看, RabbitMQ 的综合性能还是比较强的。

1.5 AMQP 和 JMS

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

1.5.1 AMQP

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

1.5.2 JMS

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

1.5.5 比较

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

1.6 RabbitMQ

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

RabbitMQ 基础架构如下图: RabbitMQ 学习(一)—— 基础知识入门_第11张图片
相关概念:

  • 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 学习(一)—— 基础知识入门_第12张图片

二、安装及配置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

RabbitMQ 学习(一)—— 基础知识入门_第13张图片
可以看出我的最高版本是 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)。
RabbitMQ 学习(一)—— 基础知识入门_第14张图片
登陆成功RabbitMQ 学习(一)—— 基础知识入门_第15张图片
角色说明:

  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 名称,然后确定就可以了。RabbitMQ 学习(一)—— 基础知识入门_第16张图片

2.6.2.2. 设置Virtual Hosts权限

找到创建号的 Virtual Hosts,通过点击其名称进入内部:
RabbitMQ 学习(一)—— 基础知识入门_第17张图片
进入之后,我们能在页面上看到 Permissions 模块,点开它,找出对用的用户,添加进来,就赋予了对应用户相应的权限。RabbitMQ 学习(一)—— 基础知识入门_第18张图片
添加成功:
RabbitMQ 学习(一)—— 基础知识入门_第19张图片

三、RabbitMQ 工作模式

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

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

3.1 简单模式

3.1.1 介绍

简单模式包括了一个生产者、一个消费者和一个消息队列。工作时,生产者向消息队列中投放消息,消费者从消息队列中消费消息:
RabbitMQ 学习(一)—— 基础知识入门_第20张图片
图解:

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

3.1.2 示例代码

3.1.2.1 pom.xml

<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.0modelVersion>

    <groupId>com.kaikebagroupId>
    <artifactId>rabbitmq-demoartifactId>
    <version>1.0-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>com.rabbitmqgroupId>
            <artifactId>amqp-clientartifactId>
            <version>5.13.1version>
        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 管控台上查看队列信息
    RabbitMQ 学习(一)—— 基础知识入门_第21张图片

RabbitMQ 学习(一)—— 基础知识入门_第22张图片

  1. 后启动消费者程序,就会在控制台输出,队列消息的相关信息。
    RabbitMQ 学习(一)—— 基础知识入门_第23张图片

3.2 工作队列模式

3.2.1 模式说明

RabbitMQ 学习(一)—— 基础知识入门_第24张图片
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. 队列 和 队列中的消息
    RabbitMQ 学习(一)—— 基础知识入门_第25张图片
    RabbitMQ 学习(一)—— 基础知识入门_第26张图片
  2. 消费者消费情况
    Consumer01 消费情况:
    RabbitMQ 学习(一)—— 基础知识入门_第27张图片
    Consumer02 消费情况:
    RabbitMQ 学习(一)—— 基础知识入门_第28张图片
    小结: 通过Consumer01 和 Consumer02 的控制台信息,我们可以看出,两个消费者各自消费了一半的消息;

3.3 订阅模式

3.3.1 模式图

RabbitMQ 学习(一)—— 基础知识入门_第29张图片
相比之前的案例和模式,订阅模式的模型中,多了一个 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. 生产者启动后的结果展示
  • 交换机
    RabbitMQ 学习(一)—— 基础知识入门_第30张图片
  • 交换机上绑定的队列
    RabbitMQ 学习(一)—— 基础知识入门_第31张图片
  • 产生的队列
    RabbitMQ 学习(一)—— 基础知识入门_第32张图片
  1. 消费者消费情况
  • Consumer01
    RabbitMQ 学习(一)—— 基础知识入门_第33张图片
  • Consumer02
    RabbitMQ 学习(一)—— 基础知识入门_第34张图片

3.4 Routing 路由模式

3.4.1 模式说明

请添加图片描述
RabbitMQ 学习(一)—— 基础知识入门_第35张图片
模式图说明:
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 示例代码

RabbitMQ 学习(一)—— 基础知识入门_第36张图片
我们声明两个队列,对应的 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 类型)
    RabbitMQ 学习(一)—— 基础知识入门_第37张图片
  • 交换机上绑定的 队列
    RabbitMQ 学习(一)—— 基础知识入门_第38张图片
  • 队列中的消息
    RabbitMQ 学习(一)—— 基础知识入门_第39张图片
  1. 消费者消费情况
  • Consumer01
    RabbitMQ 学习(一)—— 基础知识入门_第40张图片
  • Consumer02
    RabbitMQ 学习(一)—— 基础知识入门_第41张图片

3.4.3 多个 routing key

根据开始的图片,我们要是一个队列上有多个 routing key 的时候,应该怎么做呢?就是下面这个图:
RabbitMQ 学习(一)—— 基础知识入门_第42张图片
我们可以看到,上面的队列的 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. 生产者运性结果
  • 交换机
    RabbitMQ 学习(一)—— 基础知识入门_第43张图片
    我们可以看到:当一个队列有多个 routing key 的时候,RabbitMQ 是分别创建绑定关系的,不同的队列可以绑定相同的 routing key,不会冲突,也不会出错。
    RabbitMQ 学习(一)—— 基础知识入门_第44张图片

  • 队列中的消息
    队列routing_queue_01:只收到一条消息
    RabbitMQ 学习(一)—— 基础知识入门_第45张图片
    队列routing_queue_02:收到了两条消息
    RabbitMQ 学习(一)—— 基础知识入门_第46张图片

  1. 消费者结果
  • Consumer01
    RabbitMQ 学习(一)—— 基础知识入门_第47张图片

  • Consuemr02
    RabbitMQ 学习(一)—— 基础知识入门_第48张图片

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。
RabbitMQ 学习(一)—— 基础知识入门_第49张图片
RabbitMQ 学习(一)—— 基础知识入门_第50张图片

图解:
红色 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. 消费者启动之后
  • 交换机和绑定关系
    RabbitMQ 学习(一)—— 基础知识入门_第51张图片

RabbitMQ 学习(一)—— 基础知识入门_第52张图片

  • 队列信息
    RabbitMQ 学习(一)—— 基础知识入门_第53张图片
  1. 生产者启动
  • Consumer01
    RabbitMQ 学习(一)—— 基础知识入门_第54张图片

  • Consumer02
    RabbitMQ 学习(一)—— 基础知识入门_第55张图片

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将消息发送到对应的队列 。

你可能感兴趣的:(RabbitMQ,MQ,rabbitmq,分布式)