1.1 结构概念

1.1.1 RabbitMQ架构

RabbitMQ——2、结构概念_服务器

1.1.2 概念

1.1.2.1 Message

消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

1.1.2.2 Publisher

\1. 消息的生产者,也是一个向交换器发布消息的客户端应用程序。

1.1.2.3 Exchange(将消息路由给队列 )

\2. 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

1.1.2.4 Binding(消息队列和交换器之间的关联)

\3. 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

1.1.2.5 Queue

\4. 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

1.1.2.6 Connection

\5. 网络连接,比如一个 TCP 连接。

1.1.2.7 Channel

\6. 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

1.1.2.8 Consumer

\7. 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

1.1.2.9 Virtual Host

\8. 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。

1.1.2.10 Broker

\9. 表示消息队列服务器实体。

1.1.3 Exchange类型

Exchange 分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:

1.1.3.1 Direct 键(routing key)分布:

\1. Direct:消息中的路由键(routing key)如果和 Binding 中的 binding key 一致,交换器就将消息发到对应的队列中。它是完全匹配、单播的模式。

RabbitMQ——2、结构概念_封装_02

1.1.3.2 Fanout(广播分发)

\2. Fanout:每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。RabbitMQ——2、结构概念_消息队列_03

1.1.3.3 topic 交换器(模式匹配)

\3. topic 交换器:topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“”。#匹配 0 个或多个单词,匹配不多不少一个单词。

RabbitMQ——2、结构概念_封装_04RabbitMQ——2、结构概念_发送消息_05

1.1.4 消息模型

1.1.4.1 基本消息模型

生产者+消费者+队列

RabbitMQ是一个消息代理:它接受和转发消息。 你可以把它想象成一个邮局:当你把邮件放在邮箱里时,你可以确定邮差先生最终会把邮件发送给你的收件人。 在这个比喻中,RabbitMQ是邮政信箱,邮局和邮递员。

RabbitMQ与邮局的主要区别是它不处理纸张,而是接受,存储和转发数据消息的二进制数据块。

RabbitMQ——2、结构概念_封装_06

\1) P(producer/ publisher):生产者,一个发送消息的用户应用程序。

\2) C(consumer):消费者,消费和接收有类似的意思,消费者是一个主要用来等待接收消息的用户应用程序

\3) 队列(红色区域):rabbitmq内部类似于邮箱的一个概念。虽然消息流经rabbitmq和你的应用程序,但是它们只能存储在队列中。

队列只受主机的内存和磁盘限制,实质上是一个大的消息缓冲区。

许多生产者可以发送消息到一个队列,许多消费者可以尝试从一个队列接收数据。

总之:

生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。

使用方式:

发送消息:

通过工具类得到connection,可直接调用createChannel方法得到一个channel通道,这个通道设置好队列的名称,通过basicPublish方法来发送消息,最后要关闭通道和连接

发送消息后,可以在rabbitmq的主页上,看到queues界面中,有1条消息,已经准备好

接收消息:

同样是得到connection连接和channel通道(同样的队列名称),

但是要创建一个DefaultConsumer消费者来封装通道,内部重写handleDelivery方法,用来封装处理消息的逻辑,类似于监听,如果有消息过来了,就会被调用,内部可封装消息的打印的逻辑

channel通道要调用basicConsume方法,传入DefaultConsumer消费者,将队列与消费者进行绑定

最后这个channel要将消费者与队列进行绑定,并设置是否自动进行消息确认

只要消费者没有关闭channel通道和connection连接,会一直监听这个队列,只要有消息过来,就会执行,这里是直接打印消息

RabbitMQ怎么知道消息被接收了呢?

如果消费者领取消息后,还没执行操作就挂掉了呢?或者抛出了异常?消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了!

因此,RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:

• 自动ACK:消息一旦被接收,消费者自动发送ACK

• 手动ACK:消息接收后,不会发送ACK,需要手动调用

使用哪种消息确认方式,需要看消息的重要性:

• 如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便

• 如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。

手动ACK:

1、需要在消费者的handleDelivery方法中,封装处理的消息逻辑时,同时使用channel通道调用basicAck方法确认需要ack

channel.basicConsume(QUEUE_NAME, false, consumer);

如果第二个参数为true,则会自动进行ACK;如果为false,则需要手动ACK。

2、在最后通道进行队列和消费者绑定的时候,将是否自动进行消息确认设置为false

消费者出现异常:

自动ACK在消费者出现异常的情况下,仍然会队列中生产者传过来的消息,

而手动ACK则会在rabbitmq主页的queues界面中,将消息变成为非确认的,即unacked的,当关闭消费者后,会自动转为ready,即消息准备阶段

1.1.4.2 work消息模式

一个生产着,多个消费者共享消息队列,即还是一个消费者只能处理一个消息,这样可以避免消息堆积

RabbitMQ——2、结构概念_封装_07

发送密集型消息时,使用多个消费者进行处理,即多个消费者共享消息队列,但不是共享消息,即还是一个消费者只能处理一个消息,这样可以避免消息堆积

使用:

发送消息

生产者与案例1中的几乎一样:只不过变成了循环发送消息:

即获得connection连接和channel通道后,channel通道设置了队列的名称后,发送消息,不是只发送一条消息,而是循环发送消息,然后关闭通道和连接

接收消息:

消费者同上,只不过是启动了两个消费者实例,且监听的是相同的队列名字

如果消费者实例处理消息的时间花费不同,需要让处理速度快的消费者处理更多的消息,就需要在每个消费者实例中设置每次只能处理一条消息

RabbitMQ——2、结构概念_消息队列_08

1.1.4.3 订阅模型分类

一个生产者,一个交换机,多个消息队列,多个消费者,消费者处理的都是相同的消息,就需要添加交换机

前面两种模式,消费者处理的是不同的消息,如果要求消费者处理的都是相同的消息,就需要添加交换机

订阅模型示意图:

RabbitMQ——2、结构概念_发送消息_09

解读:

1、1个生产者,多个消费者

2、每一个消费者都有自己的一个队列

3、生产者没有将消息直接发送到队列,而是发送到了交换机

4、每个队列都要绑定到交换机

5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的

交换机的作用

X(Exchanges)交换机作用:

1、接收生产者发送的消息。

2、知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。

交换机的类型

Exchange类型有以下几种:

1、 Fanout:广播,将消息交给所有绑定到交换机的队列

2、 Direct:定向,处理路由键,把消息交给符合指定routing key 的队列,要求该消息与一个特定的路由键完全匹配,就是将不同的消息发送给不同的队列,即特定的消费者处理特定的消息

需要指定routing key,key为error的发送给消费者1,为success的发送给消费者2;

如果一个队列绑定到该交换机上要求路由键 “abc”,则只有被标记为“abc”的消息才被转发,不会转发abc.def,也不会转发dog.ghi,只会转发abc。

3、 Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Exchange****(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

消息模型

交换机的三种类型,也就是余下的三种消息模型

交换器4种类型?

主要有以下4种。

\1) fanout:把所有发送到该交换器的消息,路由到所有与该交换器绑定的队列中。

\2) direct:把消息路由到BindingKey和RoutingKey完全匹配的队列中。

\3) topic:匹配规则:

a) RoutingKey 为一个 点号'.': 分隔的字符串。比如: java.xiaoka.show

b) BindingKey和RoutingKey一样也是点号“.“分隔的字符串。

c) BindingKey可使用 * 和 # 用于做模糊匹配,*匹配一个单词,#匹配多个或者0个

\4) headers:不依赖路由键匹配规则路由消息。是根据发送消息内容中的headers属性进行匹配。性能差,基本用不到。

1.1.4.4 广播模式使用:

生产者:

同上,只不过,channel通道绑定的不是队列,而是交换机,通道绑定交换机的时候,要指定交换机的类型

消费者:

channel仍然要绑定队列,但同时还要将队列和交换机一起绑定,且绑定的是与生产者交换机相同名字的交换机,

然后再创建消费者,重写封装处理消息逻辑的方法,

最后,通道要将队列和消费者绑定起来,并设置是否自动进行消息确认

1.1.4.5 定向模式使用:

生产者:

channel通道绑定交换机后,要指定交换机的类型为direct

通道在发送消息的时候,不仅要设置交换机的名字和要发送的消息,还要指定一个routing key

消费者:

内部在绑定队列后,绑定交换机的时候,需要指定routing key,即表示本消息队列接收指定routing key消息,实现了,特定的消息队列处理特定的消息

1.1.4.6 通配符模式:

与定向模式相同,只不过指定routing key的时候,可以使用通配符,而不是明确的指定为error、success等