AMQP 和IM的区别

AMQP:  

1、可以一对多广播,也可以一对一广播

2、生产者和消费者不知道对方是谁


IM

1、只能一对一广播

2、生产者和消费者知道对方是谁



RabbitMQ:只是消息代理

  • 我们不生产消息,我们只是消息的搬运工

  • 每条消息只会发送给一个订阅者(一个萝卜一个坑)


消息的传递过程:

生产者==>MQ==>消费者


消息

  • 有效载荷:需要传输的数据内容本身

  • 标签:用来告诉Rabbit消息应该投递给谁


MQ的相关概念

  • 交换器:用于接收生产者发送过来的消息    

  • 队列:用来存储生产者发送过来的消息,供消费者消费,可以理解为装消息的容器

  • 绑定:用来决策交换器将消息发送到哪个队列上


如果用乘坐火车打比方,可以这样理解RabbitMQ

乘客是消息,火车站进站安检口相当于交换器,负责接收乘客,并告诉乘客自己应该到哪个检票口等候

检票口排队的乘客相当于队列,乘坐同一班次列车的乘客在同一个检票口检票进站

乘客的手上的火车票相当于绑定(路由键),用来告诉乘客该坐哪一班次的列车,该从哪一个检票口进站。

火车车厢里的每一个座位就相当于消费者,一个座位只能给一个乘客坐



队列

什么是队列?

队列(Queues)是你的消息(messages)的终点,可以理解成装消息的容器。消息就一直在里面,直到有客户端(也就是消费者,Consumer)连接到这个队列并且将其取走为止


队列相关命令:

basic.consume持续订阅消息,将信道设置为接受模式。消息一到达队列,消费者就接收消息,可以持续接收多条消息

basic.get订阅单条消息,接收消息后,取消订阅。这样做是为了让消费者只接收下一条消息,如果想获得更多消息,需要再次发送basic.get。

basic.ack消费者接收消息后,向RabbitMQ发送确认,表示已经收到消息

auto_ack:消费者在订阅队列是如果设置为auto_ack,表示消费者一旦接收消息后,RabbitMQ自动默认为消费者确认收到了消息。

basic.reject消费者拒绝接收MQ发送过来的消息。RabbitMQ2.0版本后新增的命令

requeue:

true:如果requeue参数设置为true,则RabbitMQ将消息重新发送给下一位消费者

false:如果设置为false,则RabbitMQ将消息从队列移除

queue.declare创建队列,如果不指定队列名称,则Rabbit会分配一个随机名称,并在命令的响应中返回

exclusive:如果设置为true,列队变为私有的,即一个队列只有一个消费者

aotu-delete:当最后一个消费者取消订阅后,队列会自动移除。如果需要临时创建一个队列,可以结合exclusive使用。消费者断开连接时,队列自动移除


队列工作原理

多个消费者订阅同一队列时,怎么发送消息?

消费者A和B同时订阅了seed_bin队列

1、消息Message_A到达队列seed_bin

2、RabbitMQ把Message_A发送给A

3、A确认接收到了消息Mesage_A

4、RabbitMQ将消息Message_A从队列seed_bin删除

5、消息Message_B到达队列seed_bin

6、RabbitMQ把Message_B发送给B

7、B确认接收到了消息Mesage_B

8、RabbitMQ将消息Message_B从队列seed_bin删除

注意

如果消费者收到一条消息后,从RabbiMQ队列断开了(或者说取消了订阅),一直没有发送确认。那么MQ认为这条消息没有发送,然后会重新将该消息发送给下一位消费者。这样做的好处是如果接收消息的程序出现问题,或者有bug,导致没有发送确认,这样RabbitMQ不会再给该应用发送消息。因为RabbitMQ会认为你的程序还没有准备好接收下一条消息,这样可以防止消息源源不断的涌向你的应用,到导致过载



交换器

什么是交换器?

交换器是服务器接收到生产者发送过来的消息后,根据不同的规则,将消息投递到不同的队列的工具。这些规则被称为路由键。队列是通过路由键绑定到交换器的。当生产者将消息发给RabbitMQ代理服务器时,消息将拥有一个路由键,Rabbit会将其与绑定的路由键践行匹配,如果匹配则投递到该队列,如果匹配不到任何绑定模式,则消息进入黑洞。



交换器的类型

  • direct:如果路由键匹配,则直接投递到对应的队列

  • fanout:不处理路由键,向所有与之绑定的队列投递消息

  • topic:处理路由键,按模式匹配,向符合规则的队列投递消息

  • headers:允许匹配消息的header,而非路由键,除此之外,direct完全一致,但性能差很多,基本不用了。


direct:

 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog


fanout:

 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。


topic:

将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。请参考下图

wKiom1gupJOiP0XsAADADnRuF3I943.png



虚拟主机

每一个RabbitMQ都能创建虚拟消息服务器,我们称之为虚拟主机(vhost)。

每一个vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定。更重要的是他们拥有自己的权限机制。这使得你能够安全的使用一个RabbitMQ服务器来服务于众多应用。vhost之于RabbitMQ就像虚拟机之于物理服务器一样。

用户只能在虚拟主机的粒度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。默认的账号密码都为guest,为了安全起见,应该改掉。

注意:在RabbitMQ上创建vhost时,整个集群都会创建该vhost。


如何创建vhost?

rabbitmqctl add_vhost [vhost_name]


如何删除vhost?

rabbitmqctl delete_vhost [vhost_name]


如何查看服务器上运行的vhost?

rabbitmqctl list_vhosts


如何管理远程的RabbitMQ节点?

rabbitmqctl -n rabbit@server_name

server_name可以是IP地址



持久化

默认情况下,当RabbitMQ服务器重启后,那些队列和交换器就都会消失。原因在于每个队列和交换器的druable属性。该属性默认情况下为false,它决定了RabbitMQ是否需要在崩溃或者重启后重新创建队列。将它设置为true,就不需要再服务器重启后重新创建队列和交换器了。


如果你以为只需要将队列和交换器的durable属性设置为true,就可以让消息幸免于重启就错了,能从AMQP中恢复崩溃的消息,我们称之为持久化。在消息发布前,通过把它的“Delivery Mode”(投递模式)设置为2,也就是持久的(persistent)即可。

注意:将投递模式设置为2,仅仅表示消息被标识为持久化的,它还必须被发布到持久化的交换器,并投递都持久化的队列中才行。否则,包含持久化的队列和交换器会在RabbitMQ重启后小时,而消息变成孤儿。因此,实现持久化必须:

  • 1.    将交换机设成 durable。

  • 2.    将队列设成 durable。

  • 3.    将消息的 Delivery Mode 设置成2 


交换器队列可以设置为durable,那绑定(Bindings)怎么办?我们无法在创建绑定的时候设置成durable。没问题,如果你绑定了一个durable的队列和一个durable的交换机,RabbitMQ会自动保留这个绑定。类似的,如果删除了某个队列或交换机(无论是不是durable),依赖它的绑定都会自动删除。

注意两点:

  • RabbitMQ 不允许你绑定一个非坚固(non-durable)的交换机和一个durable的队列。反之亦然。要想成功必须队列和交换机都是durable的

  • 一旦创建了队列和交换机,就不能修改其标志了。例如,如果创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重现创建。因此,最好仔细检查创建的标志


RabbitMQ持久化的机制

RabbitMQ能确保持久性消息能够在服务器重启后恢复的方式是,将消息写入磁盘上的一个持久性的日志文件中。当发布一条持久性消息到持久化交换器上时,Rabbit会先将消息发送到持久化日志文件,然后再发送到持久化交换器。如果说这条消息路由到非持久化的队列上,它会自动从持久性日志中移除,重启后无法恢复。


性能

对于一个需要在重启之后恢复的消息来说,它需要被写入到磁盘上,这样性能就会大打折扣。写入磁盘要比写入内存慢的不止一点点,而且会极大的减少RabbitMQ服务器每秒可以处理的消息数。另外,持久消息在RabbitMQ内建集群环境下工作得并不好。RabbitMQ集群的任何一个节点上都没有备份的拷贝,这意味着运行某一队列的集群节点崩溃了,在该节点恢复之前,这个队列也就从集群队列中消失了。


事务/confirm模式

RabbitMQ使用事务会降低大约2~10倍的消息吞吐量,几乎吸干了RabbitMQ的性能。后来RabbitMQ团队拿出了更好的加爵方案==>“发送方确认模式”(confirm)。需要将RabbitMQ信道设置为confirm模式,并且一旦设置成为confirm模式后,必须通过重新创建信道来关闭该设置。


confirm模式工作机制

一旦信道进入confirm模式,所有信道上发布的消息都会被指派一个唯一的ID号(从1开始)。一旦消息被投递给所有匹配的的队列后,信道会发送一个发送方确认模式给生产者(包含消息的ID),这样生产者就知道消息被安全送到目标队列了。如果消息和队列是可持久化的,那么确认消息只会在队列将消息写入磁盘后才会发出。发送方确认模式的最大好处是异步的。一旦发布了消息,生产者可以在等待确认的同时继续发送下一条消息。当确认消息最终收到的时候,生产者应用程序的回调方法就会被触发来处理该确认消息。如果RabbitMQ发生了内部错误从而导致消息丢失,Rabbit会发送一条nack(not acknowledge,未确认)消息。就像发送确认消息那样,只不过这次明确说明消息已经丢失。由于没有事务回滚的概念,因此发送方确认模式更加轻量级,同时,对RabbitMQ代理服务器的性能几乎可以忽略不计。