目录
MQ(Message Queue),本质上是个FIFO先入先出的队列,只不过队列中存放的内容是message而已,还是一种跨进程的通信机制,用于上下游传递消息。
RabbitMQ是一个消息中间件:它接受并转发消息,接收、存储和转发消息数据。
工作队列的主要思想是避免立即执行资源密集型任务,我们把任务封装为消息并将其发送到队列,在后台运行的工作进程将弹出任务并最终执行任务。当有多个工作线程(即消费者)时,这些工作线程将一起处理这些任务。
注意:处于同一队列的工作线程之间是竞争关系,同一个消息不能被重复消费。
消费者按照顺序有序地一个接收一次消息。
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它就挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。为了保证消息在发送过程中不丢失,RabbitMQ引入消息应答机制,即消费者在接收到消息并且处理该消息之后,告诉RabbitMQ它已经处理了,RabbitMQ可以把该消息删除了。
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收之前,消费者那边出现连接或者channel关闭,那么消息就丢失了;另一方面这种模式生产者那边可以传递过载的消息,没有对传递的消息数量进行限制,有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽。这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。
手动应答的好处是可以批量应答并且减少网络拥堵。
Multiple的true和false含义:
如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失),导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将其重新排队。如果此时其他消费者可以处理,它将很快将其分发给另一个消费者,这样即使某个消费者偶尔死亡,也确认不会丢失任何消息。
如何保证党RabbitMQ服务停掉以后消息生产者发送过来的消息不丢失。默认情况下RabbitMQ退出或由于某种原因崩溃时,它忽视队列和消息,除非告知它不要这么做。确保消息不会丢失需要做两件事:需要将队列和消息都标记为持久化。
采用轮询分发的话,在某些场景下并不好,比如有两个消费者在处理任务,其中有一个消费者1处理任务的速度非常快,另外一个消费者2处理速度很慢,这个时候如果采用轮询分发的话就会导致处理速度快的这个消费者很大一部分时间处于空闲状态,资源不能充分利用。
避免上述情况,在消费者设置一个参数channel.basicQos(1); 意思是如果这个任务当前消费者还没有处理完成或者还没有应答,那么就先不给这个消费者分配,此时RabbitMQ就会把该任务分配给还没有那么忙的空闲消费者。相当于给消费者设置一个信息容量范围,仍旧是轮询每个channel,但是若此时容量满了,就会跳过该channel,实现了不公平分发。
消息的发送是异步发送的,所以在任何时候,channel上肯定不止只有一个消息,另外消费者的手动确认也是异步的,因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。
通过使用basic.qos方法设置“预取计数”值来完成,该值定义通道上允许的未确认消息的最大数量,一旦数量达到配置的数量,RabbitMQ将停止在通道上传递更多消息。
消息应答和Qos预取值对用户吞吐量有重大影响,通常,增加预取将提高向消费者传递消息的速度,虽然自动应答传输消息速率是最佳的,但是,在这种情况下已传递但尚未处理的消息的数量也会增加,从而增加了消费者的RAM消耗(随机存取存储器),应该小心使用具有无限预处理的自动确认模式或手动确认模式。
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出。
confirm模式最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢弃,就会发送一条nack消息,生产者应用程序同样可以在回调函数中处理该nack消息。
这是一种简单的确认模式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续发布,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。这种确认方式有一个最大的缺点就是发布速度特别的慢,因为如果没有确认发布的消息就会阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。
发布一批消息然后一起确认,可以极大地提高吞吐量,但是也有缺点,当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。这种方式也是同步的,也会阻塞消息的发布。
异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,它是利用回调函数来达到消息可靠性传递的,通过函数回调来保证是否投递成功。
最好的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列。
发布/订阅模式。
消息能够由路由发送到队列中其实是由routingkey(bindingkey)绑定key指定的。不同的routingkey代表不同的路由方式。
RabbitMQ消息传递模型的核心思想是生产者生产的消息从不直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递到了哪些队列中。相反,生产者只能将消息发送到交换机exchange,交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列,交换机必须确切知道如何处理收到的消息,是应该把这些消息放到特定队列,还是说把它们放到许多队列中,还是说丢弃它们,这就由交换机的类型来决定。
每当我们连接到RabbitMQ时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称的队列,或者能让服务器为我们选择一个随机队列名称那就更好了,其次一旦我们断开了消费者的连接,队列将被自动删除(不带有持久化)
binding其实是exchange和queue之间的桥梁,它告诉我们exchange和哪个队列进行了绑定关系。
Fanout这种类型非常简单,它是将接收到的所有消息广播到它知道的所有队列中。Fanout是广播模式,无论routingkey是否相同,消费者都会收到来自同一个交换机发出的消息,即把交换机里的消息发给所有绑定该交换机的队列,忽略routingkey。
direct类型的工作方式是消息只去到它绑定的routingkey队列中去。
如果exchange的类型是direct,但是它绑定的多个队列的routingkey都相同,在这种情况下,虽然绑定类型是direct,但是它表现的就和fanout有点类似了,和广播差不多。
topics的工作方式是消息只去到它能匹配的topic的队列中去。
发送到类型是topic交换机的消息的routingkey是不能随便写的,必须满足一定的要求,它必须是一个单词列表,以点号.分开,这个单词列表最多不能超过255个字节。
在这个规则列表,有两个替换符:
死信,顾名思义就是无法被消费的消息,某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就成了死信,自然就有了对应的死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到死信队列机制,当消息消费发生异常时,将消息投入死信队列中;比如说用户在商场下单成功并点击去支付后在指定时间未支付时自动失效。
延时队列,队列内部是有序的,最重要的特性体现在它的延时属性上,延时队列中的元素是希望在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。
使用场景:需要在某个事件发生之后或者之前的指定时间点完成某一项任务。
TTL是RabbitMQ中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒。换句话说,如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为死信。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。
如果不设置TTL,表示消息永远不会过期;如果设置TTL为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
两种方式设置TTL:
两者的区别:
如果使用在消息属性上设置TTL的方式,消息可能并不会按时死亡,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,如果第一消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行,因为队列的先进先出原则。
解决方法:利用RabbitMQ插件rabbitmq_delayed_message_exchange实现延时队列。 此时不再由队列或消息来延时,而是由交换机来延时,消息停留在交换机中,而不是在队列中,就不用再满足先入先出原则。
前面介绍的发布确认是在服务器正常工作下消息持久化成功之后给交换机一个确认(消费方的确认应答,重新入队);这里介绍的是在服务器挂了,即交换机、队列不能正常工作的环境下(生产方的确认应答)。两者本质上都是为了防止消息丢失。
在生产环节中由于一些不明原因,导致RabbitMQ重启,在重启期间生产者消息投递失败,导致消息丢失,需要手动处理和恢复。
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个时事件的,通过Mandatory参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为Fanout,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。也可以建立一个报警队列,用独立的消费者来进行监测和报警。
mandatory参数与备份交换机可以一起使用,如果两者同时开启,备份交换机的优先级更高。
用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
消费者在消费MQ中的消息时,MQ已经把消息发送给消费者,消费者在给MQ返回ack时网络中断,故MQ未收到确认消息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者重复消费的问题。
两者方式:
将队列设计为优先级队列,消息需要设置消息的优先级,消费者需要等待消息已经发送到队列中才去消费因为需要对消息进行排序。先把消息放到队列中,排完序后再让消费者消费。
惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或是由于维护而关闭等)而致使更长时间内不能消费造成堆积时,惰性队列就很需要了。惰性队列占的内存很小,因为内存中存放的是一些索引,通过索引到磁盘上找消息。
PS:根据尚硅谷课程进行整理,如有侵权,联系删除。