RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。
RabbitMQ模型更像是一个交换机模型
模型架构图如下:
生产者和消费者
Producer:生产者,就是投递消息的一方。
生产者创建消息,然后发布到RabbitMQ中。消息一般包含两个部分:消息体(payload)和标签(label)。在实际情况中,消息体一般是一个带有业务逻辑结构的数据,如JSON串,也可以是这个消息的序列化操作。消息的标签用来表述这条消息,比如一个交换器的名称和一个路由键。生产者把消息交给RabbitMQ,RabbitMQ之后会根据这个标签把消息发送给感兴趣的消费者。
Consumer:消费者,就是接收消息的一方。
消费者链接到RabbitMQ服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体(payload)。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也只会消费到消息体,也就不知道消息的生产者是谁,当然消费者也不需要知道。
Broker:消息中间件的服务节点
一个RabbitMQ Broker可以简单看作一个RabbitMQ服务节点,或者RabbitMQ服务实例。也可以将其看作一台RabbitMQ服务器。
生产者将消息存入RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程如下所示:
消费者的业务处理逻辑并不一定需要和接收消息的逻辑使用同一个线程。消费者进程可以使用一个线程去接收消息存入到内存中,比如使用Java中的BlockingQueue。业务处理逻辑使用另一个线程从内存中读取数据,这样可以将应用进一步解耦。
队列
Queue:队列,是RabbitMQ的内部对象,用于存储消息。用下图表示:
RabbitMQ中消息都只能存储在队列中,生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
多个消费者可以订阅一个队列,这时队列中消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。
RabbitMQ不支持队列层面的广播消费。
Exchange:交换器
RabbitMQ中生产者并不会直接将消息发送到队列中,而是生产者将消息发送到交换器,由交换器将消息路由到一个或多个队列中。如果路由不到或许会返回给生产者,或许直接丢弃。
RabbitMQ中的交换器有4种类型,不同类型有不同的路由策略。
RoutingKey:路由键
生产者将消息发送给交换器的时候,一般会指定一个路由键,用来指定这个消息的路由规则,而这个路由键需要与交换器类型和绑定键(BindingKey)联合使用才能生效。
在交换机类型和绑定键固定的情况下,生产者可以在发送消息给交换器时,通过指定路由键来决定消息流向哪里。
Binding:绑定
RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确的将消息路由到队列了。
生产者将消息发送给交换器时,需要一个路由键,当绑定键和路由键相匹配时,消息会被路由到对应的队列中。
在绑定多个队列到同一个交换器时,这些绑定允许使用相同的绑定键。
绑定键并不是在所有情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视绑定键,而是将消息路由到所有绑定到该交换器的队列中。
BindingKey其实也属于路由键的一种,官方解释为:在绑定时候使用的路由键。
交换器类型
四种常用:fanout、direct、topic、headers
fanout
它会把所有发送到该交换器的消息,路由到所有与该交换器绑定的队列中。
direct
把消息路由到那些BindingKey和RoutingKey完全匹配的队列中。
topic
对direct进行扩展,匹配规则做如下约定:
①RoutingKey为一个点号“.”分割的字符串,被点号分割的每一段独立的字符串称为一个单词。如“com.epoint.rabbitmq”
②BindingKey和RoutingKey一样也是点号分割的字符串。
③BindingKey中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多规格单词(可以是0个)。
路由键为“com.rabbitmq.client”的消息会被路由到队列1和2中
路由键为“com.hidden.client”的消息会被路由到队列2中
路由键为“com.hidden.demo”的消息会被路由到队列2中
路由键为“java.rabbitmq.demo”的消息会被路由到队列1中
headers
该类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。该类型交换器性能会很差,而且也不实用,基本上不用。