互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第四篇

前情提示

上篇文章:《互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第三篇》,咱们分析了 RabbitMQ 开启手动ack机制保证消费端数据不丢失的时候,prefetch 机制对消费者的吞吐量以及内存消耗的影响。html

经过分析,咱们知道了 prefetch 过大容易致使内存溢出,prefetch 太小又会致使消费吞吐量太低,因此在实际项目中须要慎重测试和设置。面试

这篇文章,咱们转移到消息中间件的生产端,一块儿来看看如何保证投递到 MQ 的数据不丢失。数据库

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

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

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

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

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

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

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

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

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

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

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

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

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

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

confirm机制的代码实现

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

消息中间件全链路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%的可能性数据不丢失,可是仍是有千万分之一的几率会丢失。

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

互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第一篇
互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第二篇
互联网面试必杀:如何保证消息中间件全链路数据100%不丢失:第三篇

来源:【微信公众号 - 石杉的架构笔记】

相关文章
相关标签/搜索