RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你讲一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ 就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。

RabbitMQ 的整体模型架构如下图:

生产者和消费者

Producer:生产者,就是投递消息的一方。

生产者创建消息,然后发不到 RabbitMQ 中。消息一般可以包含 2 个部分:消息体和标签(Label)。消息体也可以称之为 payload,在实际应用中,消息体一般是一个带有业务逻辑结构的数据,比如一个 JSON 字符串。当然可以进一步对这个消息体进行序列化操作。消息的标签用来表述这条消息,比如一个交换器的名称和一个路由键。生产者把消息交由 RabbitMQ,RabbitMQ 之后会根据标签把消息发送给感兴趣的消费者(Consumer)。

Consumer:消费者,就是接收消息的一方。

消费者连接到 RabbitMQ 服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体(payload)。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也会消费到消息体,也就不知道消息的生产者是谁,当然消费者也不需要知道。

Broker:消息中间件的服务节点。

对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者 RabbitMQ 服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。

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

  首先生产者将业务方数据进行可能的包装,之后封装成消息,发送(AMQP 协议里这个动作对应的命令为 Basic.Publish)到 Broker 中。消费者订阅并接收消息(AMQP 协议里这个动作对应的命令为 Basic.Consume 或者 Basic.Get),经过可能的解包处理得到原始的数据,之后再进行业务处理逻辑。这个业务处理逻辑并不一定需要和接收消息的逻辑使用同一个线程。消费者线程可以使用一个线程去接收消息,存入到内存中,比如使用 Java 中的 BlockingQueue。业务处理逻辑使用另一个线程从内存中读取数据,这样可以将应用进一步解耦,提高整个应用的处理效率。

队列

Queue:队列,是 RabbitMQ 的内部对象,用于存储消息。

  RabbitMQ 中消息只能存储在队列中,这一点和 Kafka 这种消息队列中间件相反。Kafka 将消息存储在 topic(主题)这个逻辑层面,而相对应的队列处理逻辑只是 topic 实际存储文件中的位移标识。RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

  多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都接收到所有的消息并处理。

  RabbitMQ 不支持队列层面的广播消费,如果需要广播消费,需要在其上进行二次开发,处理逻辑会变得异常复杂,同时也不建议这么做。

交换器、路由键、绑定

Exchange:交换器。在图 2-4 中我们暂时可以理解成生产者将消息投递到队列中,实际上这个在 RabbitMQ 中不会发生。真实情况是,生产者将消息发送到 Exchange(交换器,通常也可以用大写的 "X" 来表示),由交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许直接丢弃。这里可以将 RabbitMQ 中的交换器看作一个简单的实体。

  RabbitMQ 中的交换器有四种类型,不同的类型有着不同的路由策略。

RoutingKey:路由键。生产者将消息发给交换器的时候,一般会指定一个 RoutingKey,用来指定这个消息的路由规则,而这个 RoutingKey 需要与交换器类型和 绑定键(BindingKey)联合起来才能最终生效。

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

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

  生产者将消息发送给交换器,需要一个 RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不少在所有的情况下都生效,它依赖于交换器类型,比如 fanout 类型的交换器就会无视 BindingKey,而是将消息路由到所有绑定到该交换器的队列中。

  沿用本文开头的比喻,交换器相当于投递包裹的邮箱,RoutingKey 相当于写在包裹上的地址,BindingKey 相当于包裹的目的地,当填写在包裹上的地址和实际想要投递的地址相匹配时,那么这个包裹就会被正确投递到目的地,最后这个目的地的 "主人" -- 队列可以保留这个包裹。如果填写的地址出错,邮递员不能正确投递到目的地,包裹可能会回退给寄件人,也有可能被丢弃。

  在某些情形下,RoutingKey 与 BindingKey 可以看作同一个东西。

channel.exchangeDeclare(EXCHANGE_NAME, "direct", true, false, null);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
String message = "Hello World!";
channel.basicPublic(EXCHANGE_NAME, ROUTING_KEY,
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());

  以上代码声明了一个 direct 类型的交换器,然后将交换器和队列绑定起来。注意这里使用的字样是 "ROUTING_KEY",在本该使用 BindingKey 的 channel.queueBind 方法中却和 channel.basicPublish 方法同样使用了 RoutingKey,这样做的潜台词是:这里的 RoutingKey 和 BindingKey 是同一个东西。在 direct 交换器类型下,RoutingKey 和 BindingKey 需要完全匹配才能使用,所以上面的代码中采用了这种写法会显得方便许多。

  但是在 topic 交换器类型下,RoutingKey 和 BindingKey 之间需要做模糊匹配,两者并不是相同的。

  BingingKey 其实也属于路由键的一种,官方解释为:the routing key to use for the binding。可以翻译为:在绑定的时候使用的路由键。大多数时候,包括官方文档和 RabbitMQ Java API 中都把 BindingKey 和 RoutingKey 看作 RoutingKey,为了避免混淆,可以这么理解:

  在绑定的时候,其中需要的路由键是 BindingKey。涉及的客户端方法如: channel.exchangeBind、channel.queueBind,对应 AMQP 命令为 Exchange.Bind、Queue.Bind。

  发送消息的时候,其中需要的路由键是 RoutingKey。涉及的客户端方法如 channel.basicPublish,对应的 AMQP 命令为 Basic.Publish。

  由于某些历史的原因,包括现存能搜集到的资料显示:大多数情况下习惯性地将 BindingKey 写成 RoutingKey,尤其是在使用 direct 类型的交换器的时候。

交换器类型

RabbitMQ 常用的交换器类型有 fanout、direct、topic、headers 这四种。AMQP 协议里还提到另外两种类型:System 和自定义。

fanout

它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。

direct

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

以下图为例,交换器的类型为 direct,如果我们发送一条消息,并在发送消息的时候设置路由键为 "warning",消息会路由到 Queue1 和 Queue2,对应的示例代码如下:

channel.basicPublish(EXCHANGE_NAME, "warning",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getbytes());

  如果 在发送消息的时候设置路由键为 "info" 或者 "debug",消息只会路由到 Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。

topic

前面讲到 direct 类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic 类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:

  RoutingKey 为一个点号 "." 分隔的字符串(被点号 "." 分隔开的每一段独立的字符串成为一个单词),如 "com.rabbitmq.client"、"java.util.concurrent"、"com.hidden.client";

  BindingKey 和 RoutingKey 一样也是点号 ".' 分隔的字符串

  BindingKey 中可以存在两种特殊字符串 "*" 和 "#",用于做模糊匹配,其中 "*" 用于匹配一个单词, "#" 用于匹配多个单词(可以是零个)

  以图 2-8 中的配置为例:

  路由键为 "com.rabbitmq.client" 的消息会同时路由到 Queue1 和 Queue2;

  路由键为 "com.hidden.client" 的消息只会路由到 Queue2;

  路由键为 "com.hidden.demo" 的消息只会路由到 Queue2;

  路由键为 "java.util.concurrent" 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。

headers

headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。

RabbitMQ 相关概念的更多相关文章

  1. 消息中间件-RabbitMq相关概念及原理介绍【图文并茂】

    消息中间件 消息中间件的作用 解耦:消息中间件在服务之间插入了一个隐含的.基于数据的接口层.两边的服务处理过程都要实现这一接口,这允许我们独立的扩展或修改两边的处理过程,只要确保他们遵守相同的规范约束 ...

  2. RabbitMQ 相关概念和方法详解

    名词解释 ConnectionFactory: 与 RabbitMQ 服务器连接的管理器. Connection: 与 RabbitMQ 服务器的连接. Channel: 与 Exchange 的连接 ...

  3. 3.RabbitMQ相关概念的杂谈

    1.vhost,为什么我要有这个vhost呢? 这是因为可能有很多组使用RabbitMQ,有产品组,用户组,vhost,虚拟主机的意思,可以避免命名冲突. 2.Exchange,交换机 有四种交换机 ...

  4. RabbitMQ相关概念的理解

    1.什么是消息? 消息就是程序(服务)之间传递的数据(图/文/声/像). 2.MQ是什么? MQ(MessageQueue)是指消息队列亦或消息总线.是消息的容器,这个容器的策略是FIFO(先进先出) ...

  5. .Net RabbitMQ实战指南——RabbitMQ相关概念介绍

    什么是消息中间件 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串.JSON等,也可以很复杂,比如内嵌对象. 消息队列中间件(Message Queue Middl ...

  6. 初识RabbitMQ

    1.安装 rabbitmq官网:http://www.rabbitmq.com/ 下载地址:https://packagecloud.io/rabbitmq 下载rabbitmq-server 安装脚 ...

  7. RabbitMQ基础教程

    目录 RabbitMQ相关概念介绍 生产者和消费者 队列 交换器.路由键.绑定 交换器类型 RabbitMQ运转流程 AMQP协议介绍 AMQP生产者流转过程 AMQP消费者流转过程 安装Rabbit ...

  8. (2)RabbitMQ架构设计与应用场景

    1.什么是消息中间件? 消息是指应用间传输的数据.消息体包括文本字符串.Json.内嵌对象等.消息中间件是基于队列模型实现异步和同步传输数据的.作用:解耦,冗余(存储).扩展性.削峰.可恢复性.顺序保 ...

  9. ybc云计算思维

      YBC的云计算思维 计算机基础 一 计算机由5大单元组成 输入单元(鼠标 键盘) 存储单元(硬盘 内存) 逻辑单元(CPU) 控制单元(主板) 输出单元(显示器 音响 打印机) CPU CPU主要 ...

随机推荐

  1. python中__name__属性的使用

    python常用模块目录 1.打印出函数名字而非函数名对应的地址 )打印的是函数地址 def func(): print("我是%s函数"%func) func() ------- ...

  2. zip命令详解

    基础命令学习目录首页 好文链接:https://www.cnblogs.com/yinzhengjie/p/6247833.html 原文链接:https://www.cnblogs.com/ferr ...

  3. Python数据分析工具库-Numpy 数组支持库(二)

    1 shape变化及转置 >>> a = np.floor(10*np.random.random((3,4))) >>> a array([[ 2., 8., 0 ...

  4. Redis学习(一):CentOS下redis安装和部署

    1.基础知识  redis是用C语言开发的一个开源的高性能键值对(key-value)数据库.它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止redis支持的键值数据类型如下字符串.列表 ...

  5. VirtualBox安装增强功能

    一.安装依赖包 #yum install kernel-headers #yum install kernel-devel #yum install gcc* #yum install make 二. ...

  6. sprint冲刺(第二天)

    今天的每日立会是在早上早餐后8点在宿舍讨论的,大概讨论了关于四则运算练习器APP的看法,也对一些较为基础的功能进行说明

  7. Internet History, Technology and Security (Week3)

    Week3. Welcome to week 3! This is our fourth and final week of History where we make the connection ...

  8. jsp 页面和 jsp标记

    一个jsp页面可由5种元素组成 html标记 变量和方法的声明 java程序片 java表达式 <%!变量和方法的声明%> 被声明的方法和变量在整个jsp页面都可以访问,为全局变量 当多个 ...

  9. 学习mysql触发器遇到的问题

    在 mysql.exe 下面运行的.如果是的话, 可能是需要加一个 定义 DELIMITER // 意思是告诉 mysql , 遇到 // 符号以后, 才认为语句结束了. 否则 mysql 遇到 分号 ...

  10. MYSQL 碎片查询

    查询库中碎片大于1G的所有表 数据总大小:DATA_LENGTH+INDEX_LENGTH实际表空间大小: TABLE_ROWS*AVG_ROW_LENGTH碎片大小:(DATA_LENGTH+IND ...