在高并发的状况下如何保证消息的可靠性?消息丢失如何解决?

工做中常常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,可是消息的可靠性如何保证一直是个很大的问题,万一消息丢了怎么办?什么状况下消息就不见了呢?下面经过这篇文章,咱们就聊聊RabbitMQ 中消息的可靠性如何解决的?web

本文分三部分说明编程

  1. mq消息丢失场景有哪些?服务器

  2. 如何避免消息丢失?网络

  3. 大厂如何解决这些问题的?并发

mq消息丢失场景有哪些?异步

首先咱们看下消息周期投递过程:ide

解决RabbitMQ消息丢失问题和保证消息可靠性高并发

咱们把该图分三部分,左中右三部分,每部分都会致使消息丢失状况:性能

1.生产者生产消息到RabbitMQ-Server 消息丢失场景ui

  1. 外界环境问题致使:发生网络丢包、网络故障等形成消息丢失

  2. 代码层面,配置层面,考虑不全致使消息丢失

发送端使用Confirm模式,方案不够严谨,好比MQ Server接收消息失败发送 nack给发送端后,发送端监听失败或者没作任何事情,消息丢失的状况;

再好比发送消息到exchange后,发下路由和queue没有绑定,消息会存在丢失状况,下面会讲到具体的例子。

2.RabbitMQ-Server中存储的消息丢失

  1. 消息没有持久化致使丢失

  2. 单节点或者集群模式没有镜像模式消息丢失

  3. 个别磁盘意外损害致使消息同步失败

  4. 机房被炸

3.RabbitMQ-Server到消费者消息丢失

  1. 消费者接收到相关消息以后,还没来得及处理就宕机了,消息丢失

如何避免消息丢失?

下面也是从三个方面介绍:

  1. 生产者生产消息到RabbitMQ-Server 可靠性保证

  2. RabbitMQ-Server中存储的消息如何保证

  3. RabbitMQ-Server到消费者消息如何不丢

1. 生产者生产消息到RabbitMQ-Server可靠性保证

这个过程,消息可能会丢,好比发生网络丢包、网络故障等形成消息丢失,通常状况下若是不采起措施,生产者没法感知消息是否已经正确无误的发送到exchange中,若是生产者能感知到的话,它能够进行进一步的处理动做,好比从新投递相关消息以确保消息的可靠性。

1.1 别担忧,有一种方案能够解决:就是 AMQP协议提供的一个事务机制

RabbitMQ客户端中Channel 接口提供了几个事务机制相关的方法: channel.txSelect channel.txCommit channel.txRollback 源码截图以下:com.rabbitmq.client 包中public interface Channel extendsShutdownNotifier {}接口

在生产者发送消息以前,经过channel.txSelect开启一个事务,接着发送消息, 若是消息投递server失败,进行事务回滚channel.txRollback,而后从新发送, 若是server收到消息,就提交事务channel.txCommit可是,不多有人这么干,由于这是同步操做,一条消息发送以后会使发送端阻塞,以等待RabbitMQ-Server的回应,以后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大下降。

1.2 不过幸运的是RabbitMQ提供了一个改进方案,即发送方确认机制(publisher confirm)

首先生产者经过调用channel.confirmSelect方法将信道设置为confirm模式,一旦信道进入confirm模式,全部在该信道上面发布的消息都会被指派一个惟一的ID(从1开始),一旦消息被投递到全部匹配的队列以后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的惟一deliveryTag和multiple参数),这就使得生产者知晓消息已经正确到达了目的地了。

其实Confirm模式有三种方式实现:

  1. 串行confirm模式:producer每发送一条消息后,调用waitForConfirms()方法,等待broker端confirm,若是服务器端返回false或者超时时间内未返回,客户端进行消息重传。

  2. 批量confirm模式:producer每发送一批消息后,调用waitForConfirms()方法,等待broker端confirm。

  3. 异步confirm模式:提供一个回调方法,broker confirm了一条或者多条消息后producer端会回调这个方法。 咱们分别来看看这三种confirm模式

串行confirm

批量confirm模式

上面代码是简单版本的,生产环境绝对不是循环发送的,而是根据业务状况, 各个客户端程序须要按期(每x秒)或定量(每x条)或者二者结合来pubish消息,而后等待服务器端confirm。相比普通confirm模式,批量能够极大提高confirm效率。

可是有没有发现什么问题?

问题1: 批量发送的逻辑复杂话了。

问题2: 一旦出现confirm返回false或者超时的状况时,客户端须要将这一批次的消息所有重发,这会带来明显的重复消息数量,而且,当消息常常丢失时,批量confirm性能应该是不升反降的。

异步confirm模式

异步模式须要本身多写一部分复杂的代码实现,异步监听类,监听server端的通知消息,异步的好处性能会大幅度提高,发送完毕以后,能够继续发送其余消息。 MQServer通知生产端ConfirmListener监听类:用户能够继承接口实现本身的实现类,处理消息确认机制,此处继承类代码省略,就是上面 ProxiedConfirmListener 类: 下面贴下要实现的接口:

上面的接口颇有意思,若是是你的话,怎么实现? 消息投递前如何存储消息,ack 和 nack 如何处理消息?

下面看下异步confirm的消息投递流程:

解决RabbitMQ消息丢失问题和保证消息可靠性

解释下这张图片:

channerl1 连续发类1,2,3条消息到RabbitMQ-Server,RabbitMQ-Server通知返回一条通知,里面包含回传给生产者的确认消息中的deliveryTag包含了确认消息的序号,此外还有一个参数multiple=true,表示到这个序号以前的全部消息都已经获得了处理。这样客户端和服务端通知的次数就减小类,提高类性能。

加点消息存储和删除逻辑

事务机制和publisher confirm机制确保的是消息可以正确的发送至RabbitMQ,这里的“发送至RabbitMQ”的含义是指消息被正确的发往至RabbitMQ的交换器,若是此交换器没有匹配的队列的话,那么消息也将会丢失,怎么办?

这里有两个解决方案,

1. 使用mandatory 设置true

2. 利用备份交换机(alternate-exchange):实现没有路由到队列的消息

咱们看下RabbitMQ客户端代码方法

Channel 类中 发布消息方法

void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] div) throws IOException;

解释下:basicPublish 方法中的,mandatory和immediate

/** * 当mandatory标志位设置为true时,若是exchange根据自身类型和消息routeKey没法找到一个符合条件的queue, 那么会调用basic.return方法将消息返回给生产者<br> * 当mandatory设置为false时,出现上述情形broker会直接将消息扔掉。 */ @Setter(AccessLevel.PACKAGE) private boolean mandatory = false; /** * 当immediate标志位设置为true时,若是exchange在将消息路由到queue(s)时发现对于的queue上没有消费者, 那么这条消息不会放入队列中。 当immediate标志位设置为false时,exchange路由的队列没有消费者时,该消息会经过basic.return方法返还给生产者。 * RabbitMQ 3.0版本开始去掉了对于immediate参数的支持,对此RabbitMQ官方解释是:这个关键字违背了生产者和消费者之间解耦的特性,由于生产者不关心消息是否被消费者消费掉 */ @Setter(AccessLevel.PACKAGE) private boolean immediate;

因此为了保证消息的可靠性,须要设置发送消息代码逻辑。若是不单独形式设置mandatory=false

使用mandatory 设置true的时候有个关键点要调整,生产者如何获取到没有被正确路由到合适队列的消息呢?经过调用channel.addReturnListener来添加ReturnListener监听器实现,只要发送的消息,没有路由到具体的队列,ReturnListener就会收到监听消息。

channel.addReturnListener(new ReturnListener() { public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP .BasicProperties basicProperties, byte[] div) throws IOException { String message = new String(div); //进入该方法表示,没路由到具体的队列//监听到消息,能够从新投递或者其它方案来提升消息的可靠性。 System.out.println("Basic.Return返回的结果是:" + message); } });

此时有人问了,不想复杂化生产者的编程逻辑,又不想消息丢失,那么怎么办? 还好RabbitMQ提供了一个叫作alternate-exchange东西,翻译下就是备份交换器,这个干什么用呢?很简单,它能够将未被路由的消息存储在另外一个exchange队列中,再在须要的时候去处理这些消息。

那如何实现呢?

简单一点能够经过webui管理后台设置,当你新建一个exchange业务的时候,能够给它设置Arguments,这个参数就是 alternate-exchange,其实alternate-exchange就是一个普通的exchange,类型最好是fanout 方便管理

解决RabbitMQ消息丢失问题和保证消息可靠性

当你发送消息到你本身的exchange时候,对应key没有路由到queue,就会自动转移到alternate-exchange对应的queue,起码消息不会丢失。

下面一张图看下投递过程:

解决RabbitMQ消息丢失问题和保证消息可靠性

那么有人有个疑问,上面介绍了,两种方式处理,发送的消息没法路由到队列的方案, 若是备份交换器和mandatory参数一块儿使用,会有什么效果?

答案是:mandatory参数无效

总结下上面内容,主要如何保证消息从生产者到RabbitMQ Server 端可靠性

1. Transaction: 消息落盘,只能同步开启、提交及回滚。

2. Confirm:消息进入缓冲区,支持同步、异步、批量确认。

3. Transaction和publisher confirm机制二者是互斥的

4. 通常在生产者这块避免数据丢失,都是用 Confirm 机制的。

2.RabbitMQ-Server中存储的消息如何保证

通常消息都是存内存中的,若是消息没有持久化硬盘,一天机器须要重启,获取意外停电,重启机器后,消息全丢了,因此消息持久化是必备。

相关文章
相关标签/搜索