互联网面试必杀:如何保证消息中间件全链路数据100%不丢失(2)【石杉的架构笔记】

欢迎关注我的公众号:石杉的架构笔记(ID:shishan100)面试

周一至周五早8点半!精品技术文章准时送上!算法

目录性能优化

(1)前情提示bash

(2)ack机制回顾服务器

(3)ack机制实现原理:delivery tag网络

(4)RabbitMQ如何感知仓储服务实例宕机架构

(5)仓储服务处理失败时的消息重发并发

(6)阶段总结分布式

一、前情提示

上一篇文章互联网面试必杀:如何保证消息中间件全链路数据100%不丢失(1),咱们初步介绍了以前制定的那些消息中间件数据不丢失的技术方案遗留的问题。微服务

一个最大的问题,就是生产者投递出去的消息,可能会丢失。

丢失的缘由有不少,好比消息在网络传输到一半的时候由于网络故障就丢了,或者是消息投递到MQ的内存时,MQ突发故障宕机致使消息就丢失了。

针对这种生产者投递数据丢失的问题,RabbitMQ其实是提供了一些机制的。

好比,有一种重量级的机制,就是事务消息机制。采用类事务的机制把消息投递到MQ,能够保证消息不丢失,可是性能极差,通过测试性能会呈现几百倍的降低。

因此说如今通常是不会用这种过于重量级的机制,而是会用轻量级的confirm机制。

可是咱们这篇文章还不能直接讲解生产者保证消息不丢失的confirm机制,由于这种confirm机制其实是采用了相似消费者的ack机制来实现的。

因此,要深刻理解confirm机制,咱们得先从这篇文章开始,深刻的分析一下消费者手动ack机制保证消息不丢失的底层原理。

二、ack机制回顾

其实手动ack机制很是的简单,必需要消费者确保本身处理完毕了一个消息,才能手动发送ack给MQ,MQ收到ack以后才会删除这个消息。

若是消费者还没发送ack,本身就宕机了,此时MQ感知到他的宕机,就会从新投递这条消息给其余的消费者实例。

经过这种机制保证消费者实例宕机的时候,数据是不会丢失的。

再次提醒一下你们,若是还对手动ack机制不太熟悉的同窗,能够回头看一下以前的一篇文章:扎心!线上服务宕机时,如何保证数据100%不丢失?。而后这篇文章,咱们将继续深刻探讨一下ack机制的实现原理。

三、ack机制实现原理:delivery tag

若是你写好了一个消费者服务的代码,让他开始从RabbitMQ消费数据,这时这个消费者服务实例就会本身注册到RabbitMQ。

因此,RabbitMQ实际上是知道有哪些消费者服务实例存在的。

你们看看下面的图,直观的感觉一下:

如图不清晰请移步同名公众号 接着,RabbitMQ就会经过本身内部的一个“basic.delivery”方法来投递消息到仓储服务里去,让他消费消息。

投递的时候,会给此次消息的投递带上一个重要的东西,就是“delivery tag”,你能够认为是本次消息投递的一个惟一标识。

这个所谓的惟一标识,有点相似于一个ID,好比说消息本次投递到一个仓储服务实例的惟一ID。经过这个惟一ID,咱们就能够定位一次消息投递。

因此这个delivery tag机制不要看很简单,实际上他是后面要说的不少机制的核心基础。

并且这里要给你们强调另一个概念,就是每一个消费者从RabbitMQ获取消息的时候,都是经过一个channel的概念来进行的。

你们回看一下下面的消费者代码片断,咱们必须是先对指定机器上部署的RabbitMQ创建链接,而后经过这个链接获取一个channel。

并且若是你们还有点印象的话,咱们在仓储服务里对消息的消费、ack等操做,所有都是基于这个channel来进行的,channel又有点相似因而咱们跟RabbitMQ进行通讯的这么一个句柄,好比看看下面的代码:

另外这里提一句:以前写那篇文章讲解手动ack保证数据不丢失的时候,有不少人提出疑问:为何上面代码里直接是try finally,若是代码有异常,那仍是会直接执行finally里的手动ack?其实很简单,本身加上catch就能够了。

好的,我们继续。你大概能够认为这个channel就是进行数据传输的一个管道吧。对于每一个channel而言,一个“delivery tag”就能够惟一的标识一次消息投递,这个delivery tag大体而言就是一个不断增加的数字。

你们来看看下面的图,相信会很好理解的:

若是采用手动ack机制,实际上仓储服务每次消费了一条消息,处理完毕完成调度发货以后,就会发送一个ack消息给RabbitMQ服务器,这个ack消息是会带上本身本次消息的delivery tag的。

我们看看下面的ack代码,是否是带上了一个delivery tag?

channel.basicAck(
        delivery.getEnvelope().getDeliveryTag(), 
        false);
复制代码

而后,RabbitMQ根据哪一个channel的哪一个delivery tag,不就能够惟必定位一次消息投递了?

接下来就能够对那条消息删除,标识为已经处理完毕。

这里你们必须注意的一点,就是delivery tag仅仅在一个channel内部是惟一标识消息投递的。

因此说,你ack一条消息的时候,必须是经过接受这条消息的同一个channel来进行。

你们看看下面的图,直观的感觉一下。

其实这里还有一个很重要的点,就是咱们能够设置一个参数,而后就批量的发送ack消息给RabbitMQ,这样能够提高总体的性能和吞吐量。

好比下面那行代码,把第二个参数设置为true就能够了。

channel.basicAck(
            delivery.getEnvelope().getDeliveryTag(), 
            true);
复制代码

看到这里,你们应该对这个ack机制的底层原理有了稍微进一步的认识了。起码是知道delivery tag是啥东西了,他是实现ack的一个底层机制。

而后,咱们再来简单回顾一下自动ack、手动ack的区别。

实际上默认用自动ack,是很是简单的。RabbitMQ只要投递一个消息出去给仓储服务,那么他立马就把这个消息给标记为删除,由于他是无论仓储服务到底接收到没有,处理完没有的。

因此这种状况下,性能很好,可是数据容易丢失。

若是手动ack,那么就是必须等仓储服务完成商品调度发货之后,才会手动发送ack给RabbitMQ,此时RabbitMQ才会认为消息处理完毕,而后才会标记消息为删除。

这样在发送ack以前,仓储服务宕机,RabbitMQ会重发消息给另一个仓储服务实例,保证数据不丢。

四、RabbitMQ如何感知到仓储服务实例宕机

以前就有同窗提出过这个问题,可是其实要搞清楚这个问题,其实不须要深刻的探索底层,只要本身大体的思考和推测一下就能够了。

若是你的仓储服务实例接收到了消息,可是没有来得及调度发货,没有发送ack,此时他宕机了。

咱们想想就知道,RabbitMQ以前既然收到了仓储服务实例的注册,所以他们之间必然是创建有某种联系的。

一旦某个仓储服务实例宕机,那么RabbitMQ就必然会感知到他的宕机,并且对发送给他的还没ack的消息,都发送给其余仓储服务实例。

因此这个问题之后有机会咱们能够深刻聊一聊,在这里,你们其实先创建起来这种认识便可。

咱们再回头看看下面的架构图:

五、仓储服务处理失败时的消息重发

首先,咱们来看看下面一段代码:

假如说某个仓储服务实例处理某个消息失败了,此时会进入catch代码块,那么此时咱们怎么办呢?难道仍是直接ack消息吗?

固然不是了,你要是仍是ack,那会致使消息被删除,可是实际没有完成调度发货。

这样的话,数据不是仍是丢失了吗?所以,合理的方式是使用nack操做。

就是通知RabbitMQ本身没处理成功消息,而后让RabbitMQ将这个消息再次投递给其余的仓储服务实例尝试去完成调度发货的任务。

咱们只要在catch代码块里加入下面的代码便可:

channel.basicNack(
            delivery.getEnvelope().getDeliveryTag(), 
            true);
复制代码

注意上面第二个参数是true,意思就是让RabbitMQ把这条消息从新投递给其余的仓储服务实例,由于本身没处理成功。

你要是设置为false的话,就会致使RabbitMQ知道你处理失败,可是仍是删除这条消息,这是不对的。

一样,咱们仍是来一张图,你们一块儿来感觉一下:

六、阶段总结

这篇文章对以前的ack机制作了进一步的分析,包括底层的delivery tag机制,以及消息处理失败时的消息重发。

经过ack机制、消息重发等这套机制的落地实现,就能够保证一个消费者服务自身忽然宕机、消息处理失败等场景下,都不会丢失数据。

End

若有收获,请帮忙转发,您的鼓励是做者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用的原创系列文章正在路上

欢迎扫描下方二维码,持续关注:

石杉的架构笔记(id:shishan100)

十余年BAT架构经验倾囊相授

推荐阅读:

一、拜托!面试请不要再问我Spring Cloud底层原理

二、【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?

三、【性能优化之道】每秒上万并发下的Spring Cloud参数优化实战

四、微服务架构如何保障双11狂欢下的99.99%高可用

五、兄弟,用大白话告诉你小白都能听懂的Hadoop架构原理

六、大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问

七、【性能优化的秘密】Hadoop如何将TB级大文件的上传性能优化上百倍

八、拜托,面试请不要再问我TCC分布式事务的实现原理!

九、【坑爹呀!】最终一致性分布式事务如何保障实际生产中99.99%高可用?

十、拜托,面试请不要再问我Redis分布式锁的实现原理!

十一、【眼前一亮!】看Hadoop底层算法如何优雅的将大规模集群性能提高10倍以上?

十二、亿级流量系统架构之如何支撑百亿级数据的存储与计算

1三、亿级流量系统架构之如何设计高容错分布式计算系统

1四、亿级流量系统架构之如何设计承载百亿流量的高性能架构

1五、亿级流量系统架构之如何设计每秒十万查询的高并发架构

1六、亿级流量系统架构之如何设计全链路99.99%高可用架构

1七、七张图完全讲清楚ZooKeeper分布式锁的实现原理

1八、大白话聊聊Java并发面试问题之volatile究竟是什么?

1九、大白话聊聊Java并发面试问题之Java 8如何优化CAS性能?

20、大白话聊聊Java并发面试问题之谈谈你对AQS的理解?

2一、大白话聊聊Java并发面试问题之公平锁与非公平锁是啥?

2二、大白话聊聊Java并发面试问题之微服务注册中心的读写锁优化

2三、互联网公司的面试官是如何360°无死角考察候选人的?(上篇)

2四、互联网公司面试官是如何360°无死角考察候选人的?(下篇)

2五、Java进阶面试系列之一:哥们,大家的系统架构中为何要引入消息中间件?

2六、【Java进阶面试系列之二】:哥们,那你说说系统架构引入消息中间件有什么缺点?

2七、【行走的Offer收割机】记一位朋友斩获BAT技术专家Offer的面试经历

2八、【Java进阶面试系列之三】哥们,消息中间件在大家项目里是如何落地的?

2九、【Java进阶面试系列之四】扎心!线上服务宕机时,如何保证数据100%不丢失?

30、一次JVM FullGC的背后,竟隐藏着惊心动魄的线上生产事故!

3一、【高并发优化实践】10倍请求压力来袭,你的系统会被击垮吗?

3二、【Java进阶面试系列之五】消息中间件集群崩溃,如何保证百万生产数据不丢失?

3三、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(上)?

3四、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(中)?

3五、亿级流量系统架构之如何在上万并发场景下设计可扩展架构(下)?

3六、亿级流量架构第二弹:你的系统真的无懈可击吗?

3七、亿级流量系统架构之如何保证百亿流量下的数据一致性(上)

3八、亿级流量系统架构之如何保证百亿流量下的数据一致性(中)?

3九、亿级流量系统架构之如何保证百亿流量下的数据一致性(下)?

40、互联网面试必杀:如何保证消息中间件全链路数据100%不丢失(1)

做者:石杉的架构笔记 连接:juejin.im/post/5c263a… 来源:掘金 著做权归做者全部,转载请联系做者得到受权!

相关文章
相关标签/搜索