问题一,如何解决消息重复投递:
问题二,如何解决消息丢失:
消息在投递的时候是需要回执 的。需要返回一个确认信息。
比如 basic.ack去针对每一条消息进行回执。
但是当auto_ack设置为true的时候,那每一条消息投递完就默认是得到了接收回执。这个时候如果应用断开或者发生意外,
设置了true的情况,就会引起上述两个问题:
因为rabbitMQ 的机制是,如果默认得到回执,那么此时中断程序并不会让他认为消息投递失败,而消息投递完成之后就会删除消息,就产生了消息丢失的问题。
重复投递就是在没有收到回执的时候,比如接收到消息但是没有来得及发送ack回执,那么消息队列就会认为你没有获得这条信息,那么就会针对消费者再次投递
消息。造成消息重复投递。
另外如果发出了一个消息给不存在的队列去接受的话,也会造成消息的丢失,所以如果信道创建者如果是消费者,那么就很容易造成这样的情况。
解决确保消息不丢失:
P28
RabbitMQ崩溃或者服务器ORRabbitMQ重启的时候,消息说百分百丢失的。因为RabbitMQ一旦重启队列和交换器就会消失,那么内部的消息也就随着消失。
RabbitMQ的队列和交换器都有一个属性,durable默认为false,改为true的时候,就会让重启的RabbitMQ重新创建交换器和队列。然鹅,消息还是会丢。
所以恢复崩溃的消息,就称之为消息的持久化。这个可以在,消息发布之前,修改他的投递模式(delivery mode)设置为2,持久化。然鹅这也不会生效,这只是消息被表现为持久化,它必须发布到持久化的队列和交换器上去,否则Rabbit重启之后消息就变成了孤儿。所以想恢复崩溃后的消息,就必须:
- 把它的投递模式修改为2
- 发送到持久化的交换器
- 到达持久化的队列
确保持久性消息能从服务器重启中恢复的方式是,将他们写入磁盘的一个持久化日志里。当发布一条持久化消息到持久化交换器到时候,Rabbit会在消息提交到日志文件后才会发送响应。如果这条消息到了非持久化的队列,那么它会自动从持久性日志中移除,并且无法恢复。所以三方必须全部持久化。一旦持久化队列消费了一个持久化消息并且得到确认,RabbitMQ就会在持久化日志中把这条消息标记为等待垃圾收集。所以在你消费持久之前,如果发生宕机,重启之后,服务器会自动重建队列和交换器以及重播持久性日志中的消息。至于重播到队列还是交换器,则由宕机时间点决定。
当然了,所有的持久化都是建立在消耗性能的情况下的。这么做虽然保障了消息的不丢失,也将大幅增加消息的处理时间,因为不管如何,读写日志都要比内存慢得多。当然了,ssd还好一些。
但是这样的情况,在集群中,并不完全适用,集群中某一节点崩溃,直到恢复之前其他节点都不可用。而且该队列也会消失(如果是持久化的)。这时候的消息,依旧会消失。(第五章讲解)
和消息持久化还有一个相关的概念,那就是AMQP的事物(transaction)。之前讨论的是如何将消息,队列,交换器持久化,但是发布消息的生产者并不会得到关于消息的任何反馈。所以发布消息的一瞬间宕机你未必会知道。事物就是用来,当继续处理其他任务之前,你必须确保代理接收到了消息并且已经将消息路由给了所匹配的订阅队列。你可以把这些行为封装成一个事物。然而这个事物和熟知的事物不一样。
P31 事物固然会填补生产者发布消息最后一小段的持久化监控,但是这依旧折损了Rabbit的性能。还会导致消息的同步。
在RabbitMQ2.3.1之后,引入了一个 发送方确认模式(publisher confirm):将信道修改为confirm模式,信道上所有消息都经拥有一个ID(从1 开始),消息到了队列之后会收到一个反馈。如果是持久化,就只有消息持久化之后才会返回反馈。这个模式最大的好处就是他是异步的。发布一条消息在得到确认之前可以继续发布下一条。这样如果发生错误,消息消失,Rabbit就会发送一条back消息。如同确认消息一样,这条消息告诉生产者端刚刚消息发送失败,这样失败消息和成功消息的处理都可以交给生产者,而不需要Rabbit牺牲性能去处理这些问题。
No Leanote account? Sign up now.