RabbitMQ 是由 Erlang 语言开发,基于 AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列。它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。

RabbitMQ官方地址:http://www.rabbitmq.com

1. 下载安装 RabbitMQ

RabbitMQ 由 Erlang 语言开发,在安装 RabbitMQ 之前,需要安装与之对应版本的 Erlang 语言环境。

网上自行搜索安装教程

【注意】:本机电脑的用户名不能是中文,否则会安装失败。如果为中文,则需要修改为英文。

安装完 RabbitMQ 后,也需要安装后台管理插件 rabbitmq_managemen,我们要开启这个插件才能通过浏览器访问登录页面。

后台管理页面:http://localhost:15672
默认用户名/密码:guest/guest

这个后台管理页面咋们后续再介绍,还是先了解下 RabbitMQ 吧 ~~

2. RabbitMQ 的工作原理

2.1 RabbitMQ 的整体架构

修改RabbitMQ guest密码 rabbitmq用户名_TCP


RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储、转发消息

2.2 RabbitMQ的核心架构

下图是 RabbitMQ 的核心架构:

修改RabbitMQ guest密码 rabbitmq用户名_消息路由_02


上图的核心概念说明:

1. Broker

Broker:RabbitMQ 服务实例(安装的 rabbitmq-server);在集群下,是一个 RabbitMQ 服务节点。接受客户端的连接

如下图,生产者将消息存入 RabbitMQ 中的 Broker,以及消费者从 Broker 中消费数据的整个流程:

修改RabbitMQ guest密码 rabbitmq用户名_TCP_03


2. Virtual Host

Virtual Host:虚拟消息服务器,用于逻辑层隔离。每个 VirtualHost 相当于一个相对独立的RabbitMQ 服务器;每个 VirtualHost 之间是相互隔离的,Exchange、Queue、Message 不能互通。拿 MySQL 数据库来类比:RabbitMq 相当于 MySQL,RabbitMq 中的 Virtual Host 就相当于 MySQL 中的一个数据库;

3. Connection

Connection:连接,用于应用程序(生产者/消费者)与 Broker 的网络连接(TCP/IP 连接)

4. Channel

Channel:网络信道,几乎所有的操作都在 Channel 中进行,Channel 是进行消息读写的通道,每个通道 Channel 代表一个会话任务

5. Exchange

Exchange(不具备储存消息的能力):交换机,接收并转发消息。生产者将消息发送到交换机 Exchange,由交换机 Exchange 将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许会直接丢弃。

修改RabbitMQ guest密码 rabbitmq用户名_TCP_04

RabbitMQ 中的交换机有四种类型,不同的类型有着不同的路由策略,将会在后续中进行介绍。

6. RoutingKey
Routing key:路由键,交换机可以用它来确定如何路由一个特定消息。生产者将消息发给交换机的时候,一般会指定一个 RoutingKey,用来指定这个消息的路由规则,而这个 RoutingKey 需要与交换机类型和和绑定建 BindingKey 联合使用才能最终生效。

在交换机类型和绑定键 BindingKey 固定的情况下,生产者可以在发送消息给交换机时,通过指定路由键 RoutingKey 来决定消息流向哪里。

7. Binding

Binding:绑定,通过绑定将交换机 Exchange 与队列 Queue关联起来。在绑定的时候,一般会指定一个绑定键 BindingKey,这样,RabbitMQ 就知道如何正确地将消息路由到队列了。

生产者将消息发送给交换机时,需要一个路由键 RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换机的时候,这些绑定允许使用相同的 BindingKey。

如下图:

修改RabbitMQ guest密码 rabbitmq用户名_rabbitmq_05

但是,BindingKey 并不是在所有的情况下都生效,它依赖于交换机类型。如:fanout 类型的交换机就会无视 BindingKey,而是将消息路由到所有绑定到该交换机的队列中。

8. Queue

Queue:队列,保存消息,并转发给消费者

9. Message

Message:消息,服务于应用程序之间传递的数据,由 Properties 和 body 组成,Properties 可以对消息进行修饰,比如消息的优先级,延迟等高级特征;Body则是消息体的内容

10. Producer

Producer:消息生产者,即生产方客户端,生产方客户端将消息发送

11. Consumer

Consumer:消息消费者,即消费方客户端,接收MQ转发的消息

交换机(Exchange)一定是要有的,如果没写的话,则是会使用默认的交换机。

3. 交换机的类型

RabbitMQ 常用的交换机类型有:fanout、direct、topic、headers。

3.1 fanout

它会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中(无视 BindingKey)

3.2 direct

direct 类型的交换机路由规则也很简单,它会把消息路由到那些 BindingKey 和 RoutingKey 完全匹配的队列中

如下图:交换机的类型为 direct,如果生产者发送一条消息,并在发送消息的时候设置路由键为 warning,则消息会路由到 Q1、Q2

修改RabbitMQ guest密码 rabbitmq用户名_rabbitmq_06


如果在发送消息时,设置路由键为 info,则消息会路由到 Q2、Q3;如果设置路由键为 debug,则消息只会路由到 Q3

3.3 topic

direct 类型的交换机路由规则是完全匹配 BindingKey 和 RoutingKey,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。

topic 类型的交换机在匹配规则上进行了扩展,它与 direct 类型的交换机类似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:

  • RoutingKey 为一个点号 “.” 分割的字符串(被点号 “.” 分割开的每一段独立的字符串成为一个单词)。如:com.rabbitmq.client、java.util.concurrent
  • BindingKey 和 RountingKey 一样,也是点号 “.” 分割的字符串
  • BindingKey 中可以存在两种特殊的字符串 “*” 和 “#”,用于做模糊匹配。其中,* 用于匹配一个单词,# 用于匹配多个单词(可以是零个)

如下图:

  1. 路由键为 com.rabbitmq.client 的消息会同时路由到 Q1、Q2、Q3
  2. 路由键为 com.hidden.client 的消息会路由到 Q2、Q3
  3. 路由键为 com.hidden.demo 的消息会路由到 Q3
  4. 路由键为 java.util.concurrent 的消息会被丢弃或者返回给生产者,因为,它没有匹配任何路由键

修改RabbitMQ guest密码 rabbitmq用户名_rabbitmq_07

3.4 headers

headers 类型的交换机不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换机时,制定一组键值对,当发送消息到交换机时,RabbitMQ 会获取到该消息的 headers,对比其中的键值对是否完全匹配队列和交换机绑定时指定的键值对。如果完全匹配,则消息会路由到该队列;否则,不会路由到该队列

headers 类型的交换机性能会很差,而且也不实用,基本不会看到它的存在。

4 RabbitMQ 的运转流程

了解了以上的 RabbitMQ 架构模型及其相关术语,再来回顾整个消息队列的使用过程。

在最初状态下,生产者发送消息的时候:

  1. 生产者 Producer 连接到 Broker, 建立一个连接 Conection(TCP 连接),开启一个信道 Channel
  2. 生产者 Producer 声明一个交换机,并设置相关属性。如:交换机类型、是否持久化等
  3. 生产者 Producer 声明一个队列,并设置相关属性。如:是否持久化、自动删除等
  4. 生产者 Producer 通过绑定键 BindingKey 将交换机 Exchange 和队列 Queue 绑定起来
  5. 生产者 Producer 发送消息至 Broker,其中包括:路由键、交换器等信息
  6. 相应的交换机 Exchange 根据接收到的路由键查找相匹配的队列
  7. 如果找到,则将从生产者 Producer 发送过来的消息存入相应的队列中去
  8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者
  9. 关闭信道 Channel,连接 Connection

消费者接收消息的过程:

  1. 消费者 Consumer 连接到 Broker, 建立一个连接 Conection(TCP 连接),开启一个信道 Channel
  2. 消费者 Consumer 向 Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作
  3. 等待 Broker 回应并投递相应队列中的消息,消费者接收消息
  4. 消费者确认 ACK 接收到的消息
  5. RabbitMQ 从队列中删除相应已经被确认的消息
  6. 关闭信道、连接

【注意】:一个 Producer 发送消息,哪些 Consumer 可以收到消息,在于Exchange,RoutingKey,Queue 的关系上。

根据上述过程,无论生产者还是消费者,都需要和 RabbitMQ Broker 建立连接,这个连接就是一条 TCP 连接,即:Connection。一旦 TCP 连接建立起来,客户端紧接着可以创建一个 AMQP 信道 Channel,每个信道都会被指派一个唯一的 ID。信道 Channel 是建立在 Connection 之上的虚拟连接,RabbitMQ 处理每条 AMQP 指令都是通过信道 Channel 完成的。

如下图:

修改RabbitMQ guest密码 rabbitmq用户名_rabbitmq_08

我们完全可以直接使用 Connection 就能完成信道的工作,为什么还要引入信道 Channel 呢?

试想这样一个场景:一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么,必然需要建立很多个 Connection,也就是许多个 TCP 连接。然而,对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。RabbitMQ 采用类似于 NIO 的做法,选择 TCP 连接复用,不仅可以减少性能开销,同时也便于管理。

每个线程把持一个信道,所以,信道复用了 Connection 的 TCP 连接。同时,RabbitMQ 可以确保每个线程的私密性,就像拥有独立的连接一样。