软件开发架构师

扎心!线上服务宕机时,如何保证数据100%不丢失?

架构 27 2019-03-11 11:29

(给ImportNew加星标,提高Java技能)


本文来源:石杉的架构笔记(ID:shishan100)


一、写在前面

我们用一个简单易懂的电商场景给大家引入说明了一个消息中间件的使用场景。


同时,我们还基于RabbitMQ的HelloWorld级别的代码,给出了订单服务和仓储服务如何基于MQ中间件收发消息的示例。


二、业务场景回顾


这篇文章,我们来稍微深入探讨一些MQ中间件使用中的基础技术问题。


首先回顾一下上篇文章做出来的一个架构图,看看订单服务和消息服务是如何基于MQ来收发消息的。


我们稍微把这个图细化一点,简单来说就是多个订单服务实例给queue推送消息,多个仓储服务每个消费一部分消息。如下图所示:



三、意外宕机,问题凸现


假如你线上对MQ技术的使用就到此为止了,那么基本可以跟offer说拜拜了。。。


因为如果是我的话,作为一个面试官就没法继续往下问了。你这个MQ的使用以及理解的深度仅此而已的话,那基本就是刚刚对MQ技术入门的程度。


如果面试官要继续问,完全可以问下面的问题:


  • 那你说说如果仓储服务作为消费者服务,刚收到了一个订单消息,但是在完成消息的处理之前,也就是还没对订单完成仓储调度发货,结果这个仓储服务突然就宕机了,这个时候会发生什么事情?


所以说,大家还是要对这个技术了解的稍微深入一点点,否则随便被问几个问题就完蛋了。


大伙儿先来看看下面的图,感受一下车祸现场。



RabbitMQ这个中间件默认的一个行为,就是只要仓储服务收到一个订单消息,RabbitMQ就会立马把这条订单消息给标记为删除,这个行为叫做自动ack,也就是投递完成一条消息就自动确认这个消息处理完毕了。


但是接着如果此时仓储服务收到了一个订单消息,但是还没来得及对仓库系统完成商品的调度发货,结果直接就宕机了。


此时,明显这个订单消息就丢失了啊,因为RabbitMQ那里已经没有了。。。


这会导致什么样的尴尬体验呢?就是一个用户支付了8999元,对一个iphone8下了订单,结果呢,死等活等了好几天,就是不见网站上显示他的iphone8在发货。


搞了半天,原因就是他的那个iphone8的订单在仓储服务那里,还没来得及调度发货直接就宕机了,导致这个订单消息就一直丢失了,始终没有给这个用户通知仓库系统进行发货。


这个问题,是不是很尴尬?所以说,技术问题是会严重影响企业的核心业务流程的!


各位小伙伴,还记得上一讲咱们的仓储服务消费消息的代码中,有一行关键的代码:



这行代码对channel.basicConsume()方法,传入的第二个参数:true,其实就是一个关键的参数。


这个true就代表了一个核心的含义,他的意思是,RabbitMQ只要把一个消息投递到仓储服务手上,立马就标记这个消息删除了。


但是在这个默认的配置之下,要是仓储服务收到一个订单消息,结果还没来得及完成耗时几十秒的仓储调度发货的业务逻辑,结果突然宕机了,那么这个订单消息就永久性丢失了!


找了半天,原来问题的症结在这里啊!大家是不是明白了,上一篇文章最后为什么我会说,这个代码目前为止还有很多的问题。


所以这个时候,我们如果希望不要因为仓储服务的突然宕机导致一条订单消息丢失,就需要改造一下仓储服务消费消息的代码了。


首先,我们需要把那个参数从true改为false,如下代码所示:



只要修改为false之后,RabbitMQ就不会盲目的投递消息到仓储服务,立马就删除消息了,说白了就是关闭autoAck的行为,不要自作主张的认为消息处理成功了。


接着,我们需要改造一下处理订单消息的代码,如下代码所示。


这段代码,说白了,就是在对订单完成了调度发货之后,在finally代码块中手动执行了ack操作,说我自己已经完成了耗时几十秒的业务逻辑的处理,现在可以手动ack通知RabbitMQ,这个消息处理完毕了。



此时整个架构运行流程大致看起来跟下面的图那样子。



架构流程改成上面那样后,就意味着只有完成了仓储调度发货的代码业务逻辑,确保仓库系统收到通知之后,仓储服务才会在代码中手动发送ack消息给RabbitMQ。


此时,RabbitMQ收到了这个ack消息,才会标记对应的订单消息被删除了。


如果说在仓储服务收到了订单消息,但是还没来得及完成仓储调度发货的业务逻辑,那也就绝对不会执行这条订单消息的ack操作,然后RabbitMQ也就不会收到这条订单消息的ack通知。


一旦RabbitMQ发现代表消费者的某个仓储服务实例突然宕机了,而这个仓储服务收到的一些订单消息还没来得及处理,没给自己发送那些消息的ack通知。


此时,RabbitMQ会自动对这条订单消息重发推送给其他在运行中的仓储服务实例,让其他的仓储服务实例去处理这条订单消息。


这样的话,就可以保证这条订单消息不会因为某个仓储服务实例的宕机而丢失,他会确保必须由某个仓储服务实例完成这条订单消息的调度发货处理,然后才会删除那条订单消息。


四、总结  tips


最后再来一张图,大家直观的感受一下:



好了,各位同学,这篇文章是不是相对稍微深入一点点,让大家了解到了一些使用MQ技术时候要考虑的一些问题?


实际上无论是RocketMQ、Kafka还是RabbitMQ,都有类似的autoAck或者是手动ack的机制。


线上生产环境中运行时,你必须要考虑到消费者服务可能宕机的问题。


如果消费者服务没处理完消息就自己宕机了,那么一定会导致部分消息的丢失,进而影响核心业务流程的运转。


因此大家在线上使用MQ时,一定要充分考虑这些潜在问题,同时结合具体的MQ提供的一些API、参数来进行合理设置,确保消息不要随意丢失。


如有收获,请帮忙转发,谢谢!


作者:中华石杉,十余年BAT架构经验倾囊相授。

个人微信公众号:石杉的架构笔记(ID:shishan100)


推荐阅读

(点击标题可跳转阅读)

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

使用枚举和函数式接口实现 State 模式

埋在MySQL数据库应用中的17个关键问题!


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

喜欢就点一下「好看」呗~

文章评论