高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

若是投递出去的消息在网络传输过程当中丢失,或者在RabbitMQ的内存中还没写入磁盘的时候宕机,都会致使生产端投递到MQ的数据丢失。数据库

并且丢失以后,生产端本身还感知不到,同时还没办法来补救。网络

下面的图就展现了这个问题。架构

高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

 

 

因此本文呢,咱们就来逐步分析一下。并发

2异步

保证投递消息不丢失的confirm机制分布式

 

其实要解决这个问题,相信你们看过以前的消费端ack机制以后,也都猜到了。微服务

很简单,就是生产端(好比上图的订单服务)首先须要开启一个confirm模式,接着投递到MQ的消息,若是MQ一旦将消息持久化到磁盘以后,必须也要回传一个confirm消息给生产端。高并发

这样的话,若是生产端的服务接收到了这个confirm消息,就知道是已经持久化到磁盘了。源码分析

不然若是没有接收到confirm消息,那么就说明这条消息半路可能丢失了,此时你就能够从新投递消息到MQ去,确保消息不要丢失。性能

并且一旦你开启了confirm模式以后,每次消息投递也一样是有一个delivery tag的,也是起到惟一标识一次消息投递的做用。

这样,MQ回传ack给生产端的时候,会带上这个delivery tag。你就知道具体对应着哪一次消息投递了,能够删除这条消息。

此外,若是RabbitMQ接收到一条消息以后,结果内部出错发现没法处理这条消息,那么他会回传一个nack消息给生产端。此时你就会感知到这条消息可能处理有问题,你能够选择从新再次投递这条消息到MQ去。

或者另外一种状况,若是某条消息很长时间都没给你回传ack/nack,那多是极端意外状况发生了,数据也丢了,你也能够本身从新投递消息到MQ去。

经过这套confirm机制,就能够实现生产端投递消息不会丢失的效果。你们来看看下面的图,一块儿来感觉一下。

高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

 

 

3

confirm机制的代码实现

 

下面,咱们再来看看confirm机制的代码实现:

 

高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

 

 

 

4

confirm机制投递消息的高延迟性

 

这里有一个很关键的点,就是一旦启用了confirm机制投递消息到MQ以后,MQ是不保证何时会给你一个ack或者nack的。

由于RabbitMQ本身内部将消息持久化到磁盘,自己就是经过异步批量的方式来进行的。

正常状况下,你投递到RabbitMQ的消息都会先驻留在内存里,而后过了几百毫秒的延迟时间以后,再一次性批量把多条消息持久化到磁盘里去。

这样作,是为了兼顾高并发写入的吞吐量和性能的,由于要是你来一条消息就写一次磁盘,那么性能会不好,每次写磁盘都是一次fsync强制刷入磁盘的操做,是很耗时的。

因此正是由于这个缘由,你打开了confirm模式以后,极可能你投递出去一条消息,要间隔几百毫秒以后,MQ才会把消息写入磁盘,接着你才会收到MQ回传过来的ack消息,这个就是所谓confirm机制投递消息的高延迟性

你们看看下面的图,一块儿来感觉一下。

高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

 

 

5

高并发下如何投递消息才能不丢失

 

你们能够考虑一下,在生产端高并发写入MQ的场景下,你会面临两个问题:

  • 一、你每次写一条消息到MQ,为了等待这条消息的ack,必须把消息保存到一个存储里。

 

而且这个存储不建议是内存,由于高并发下消息是不少的,每秒可能都几千甚至上万的消息投递出去,消息的ack要等几百毫秒的话,放内存可能有内存溢出的风险。

  • 二、绝对不能以同步写消息 + 等待ack的方式来投递,那样会致使每次投递一个消息都同步阻塞等待几百毫秒,会致使投递性能和吞吐量大幅度降低。

 

针对这两个问题,相对应的方案其实也呼之欲出了。

首先,用来临时存放未ack消息的存储须要承载高并发写入,并且咱们不须要什么复杂的运算操做,这种存储首选绝对不是MySQL之类的数据库,而建议采用kv存储。kv存储承载高并发能力极强,并且kv操做性能很高。

其次,投递消息以后等待ack的过程必须是异步的,也就是相似上面那样的代码,已经给出了一个初步的异步回调的方式。

消息投递出去以后,这个投递的线程其实就能够返回了,至于每一个消息的异步回调,是经过在channel注册一个confirm监听器实现的。

收到一个消息ack以后,就从kv存储中删除这条临时消息;收到一个消息nack以后,就从kv存储提取这条消息而后从新投递一次便可;也能够本身对kv存储里的消息作监控,若是超过必定时长没收到ack,就主动重发消息。

你们看看下面的图,一块儿来体会一下:

高并发场景下,如何保证生产者投递到消息中间件的消息不丢失?

 

 

6

消息中间件全链路100%数据不丢失能作到吗?

 

到此为止,咱们已经把生产端和消费端如何保证消息不丢失的相关技术方案结合RabbitMQ这种中间件都给你们分析过了。

其实,架构思想是通用的, 不管你用的是哪种MQ中间件,他们提供的功能是不太同样的,可是你都须要考虑以下几点:

  1. 生产端如何保证投递出去的消息不丢失:消息在半路丢失,或者在MQ内存中宕机致使丢失,此时你如何基于MQ的功能保证消息不要丢失?
  2. MQ自身如何保证消息不丢失:起码须要让MQ对消息是有持久化到磁盘这个机制。
  3. 消费端如何保证消费到的消息不丢失:若是你处理到一半消费端宕机,致使消息丢失,此时怎么办?

 

目前来讲,咱们初步的借着RabbitMQ举例,已经把从前到后一整套技术方案的原理、设计和实现都给你们分析了一遍了。

可是此时真的能作到100%数据不丢失吗?恐怕未必,你们再考虑一下个特殊的场景。

生产端投递了消息到MQ,并且持久化到磁盘而且回传ack给生产端了。

可是此时MQ还没投递消息给消费端,结果MQ部署的机器忽然宕机,并且由于未知的缘由磁盘损坏了,直接在物理层面致使MQ持久化到磁盘的数据找不回来了。

这个你们千万别觉得是开玩笑的,你们若是留意留意行业新闻,这种磁盘损坏致使数据丢失的是真的有的。

那么此时即便你把MQ重启了,磁盘上的数据也丢失了,数据是否是仍是丢失了?

你说,我能够用MQ的集群机制啊,给一个数据作多个副本,好比后面咱们就会给你们分析RabbitMQ的镜像集群机制,确实能够作到数据多副本。

可是即便数据多副本,必定能够作到100%数据不丢失?

好比说你的机房忽然遇到地震,结果机房里的机器所有没了,数据是否是仍是全丢了?

说这个,并非说要抬杠。而是告诉你们,技术这个东西,100%都是理论上的指望。

应该说,咱们凡事都朝着100%去作,可是理论上是不可能彻底作到100%保证的,可能就是作到99.9999%的可能性数据不丢失,可是仍是有千万分之一的几率会丢失。

固然,从实际的状况来讲,能作到这种地步,其实基本上已经基本数据不会丢失了。

若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。

相关文章
相关标签/搜索