RabbitMQ基本概念

“松树千年终是朽,槿花一日自为荣”

上一篇说了RabbitMQ的安装,现在讲讲RabbitMQ的基本概念,先看下图,之后内容均是基于该图讲解:
RabbitMQ基本概念

生产者和消费者

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

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

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

Broker

Broker:消息中间件的服务节点。
对于 Rabbitmq来说,一个 Rabbitmq Broker可以简单地看作一个 Rabbitmq服务节点,或者 Rabbitmq服务实例。大多数情况下也可以将一个 Rabbitmq Broker看作一台 Rabbitmq服务器。
下图展示了生产者将消息存入 Rabbitmq Broker,以及消费者从 Broker中消费数据的整个流程。
RabbitMQ基本概念
首先生产者将业务方数据进行可能的包装,之后封装成消息,发送(AMQP协议里这个动作对应的命令为 Basic. Publish)到 Broker I中。消费者订阅并接收消息(AMQP协议里这个动作对应的命令为 Basic. Consume或者 Basic.Get),经过可能的解包处理得到原始的数据,之后再进行业务处理逻辑。这个业务处理逻辑并不一定需要和接收消息的逻辑使用同一个线程。消费者进程可以使用一个线程去接收消息,存入到内存中,比如使用Java中的 Blockingqueue。业务处理逻辑使用另一个线程从内存中读取数据,这样可以将应用进一步解耦,提高整个应用的处理效率。

队列

Queue:队列,是 Rabbitmq的内部对象,用于存储消息。参考图2-1,队列可以用下图表示。
RabbitMQ基本概念
Rabbitmq中消息都只能存储在队列中,这一点和 Kafka这种消息中间件相反。Kafka将消息存储在 topic(主题)这个逻辑层面,而相对应的队列逻辑只是 topic实际存储文件中的位移标识。 Rabbitmq的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
多个消费者可以订阅同一个队列,这时队列中的消息会被平均分推( Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,如图所示。
RabbitMQ基本概念
Rabbitmq不支持队列层面的广播消费,如果需要广播消费,需要在其上进行二次开发,处理逻辑会变得异常复杂,同时也不建议这么做。

路由键和绑定

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

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

Binding:绑定。 Rabbitmq中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键( Bindingkey),这样 Rabbitmo就知道如何正确地将消息路由到队列了,如图所示
RabbitMQ基本概念
生产者将消息发送给交换器时,需要一个 Routingkey,当 Bindingkey和Routingkey相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 Bindingkey。 Bindingkey并不是在所有的情况下都生效,它依赖于交换器类型,比如 fanout类型的交换器就会无视 Bindingkey,而是将消息路由到所有绑定到该交换器的队列中。

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

交换器

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

交换器类型

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

  • direct
    direct类型的交换器路由规则也很简单,它会把消息路由到那些 Bindingkey和 Routingkey 完全匹配的队列中。
    以下图为例
    RabbitMQ基本概念
    交换器的类型为 direct,如果我们发送一条消息,并在发送消息的时候设置路由键为“ warming”,则消息会路由到 Queue1和 Queue2,如果在发送消息的时候设置路由键为“info”或者“ debug”,消息只会路由到 Queue2,如果以其他的路由键发送消息,则消息不会路由到这两个队列中。

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

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

  2. Binding Key和 Routingkey一样也是点号“.”分隔的字符串

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

以下图的配置为例:
RabbitMQ基本概念
●路由键为“com. rabbitmq.client”的消息会同时路由到 Queue1和 Queue2.
●路由键为“com.hidden.client”的消息只会路由到 Oueue2中
●路由键为“com.hidden.demo”的消息只会路由到 Queue2中
●路由键为“java.rabbitmq.dcmo”的消息只会路由到 Queue1中
●路由键为“ java.util.concurrent”的消息将会被丢弃或者返回给生产者(需要设置mandatory参数),因为它没有匹配任何路由键。

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