当前位置: 首页 > 科技观察

如果我是核酸系统架构师,我会这样使用MQ

时间:2023-03-17 17:18:57 科技观察

1.上一篇文章的初步提示:《选Redis做MQ的人,是脑子里缺根弦儿吗?》,我们分析了RabbitMQ何时启用手动ack机制,以确保消费者端的数据没有丢失。预取机制对消费者吞吐量和内存消耗的影响。通过分析得知prefetch太大容易导致内存溢出,prefetch太小会导致消耗吞吐量低,需要在实际项目中仔细测试和设置。本篇我们将转到消息中间件的生产端,看看如何保证下发到MQ的数据不丢失。如果下发的消息在网络传输过程中丢失,或者RabbitMQ在内存中写入磁盘之前崩溃了,那么生产端下发给MQ的数据就??会丢失。而且,发生损失后,生产端无法感知,也无从补救。下图演示了这个问题。那么在这篇文章中,我们就一步一步来分析吧。2、保证投递消息不丢失的confirm机制其实就是要解决这个问题。相信大家看了之前的consumer端ack机制,都已经猜到了。很简单,就是生产端(比如上图中的订单服务)首先要开启一个confirm模式,然后将消息投递给MQ。如果MQ将消息持久化到磁盘,还必须向生产端返回一个确认消息。.这样生产端的服务如果收到确认消息,就知道已经持久化到磁盘了。否则,如果收不到确认消息,则意味着消息可能中途丢失了。这时候可以将消息重新投递到MQ,保证消息不会丢失。并且一旦你开启了确认模式,每条消息的投递也会有一个投递标签,也就是唯一标识一次消息投递。这样MQ在返回ack给生产端的时候,会携带delivery标签。你会知道它对应的是哪条消息传递,你可以删除这条消息。另外,如果RabbitMQ收到一条消息,发现由于内部错误导致无法处理该消息,它会向生产端返回一个nack消息。这个时候你会感知到这条消息的处理可能有问题,你可以选择再次重新投递这条消息给MQ。或者另外一种情况,如果某个消息长时间没有给你返回ack/nack,可能是发生了极端的意外,数据丢失了。也可以自己重新投递消息给MQ。通过这套confirm机制,可以达到生产端下发的消息不会丢失的效果。下面我们就来看看这张图,一起来体验一下吧。3.confirm机制的代码实现接下来我们看一下confirm机制的代码实现:4.confirm机制传递消息的高延迟这里有一个关键点,就是一旦confirm机制已启用向MQ传递消息,MQ无法保证您何时会收到ack或nack。因为RabbitMQ在内部将消息保存到磁盘,所以它通过异步批处理来实现。一般情况下,你post到RabbitMQ的消息会先驻留在内存中,然后经过几百毫秒的延时,多条消息会一次性批量持久化到磁盘。这样做是为了兼顾高并发写的吞吐量和性能,因为如果一条消息写一次磁盘,性能会很差,而且每次写磁盘都是fsyncforcing的操作要刷新的磁盘。非常耗时。所以也正是因为这个原因,你开启confirm模式后,很有可能你post了一条消息,MQ会在间隔几百毫秒后将消息写入磁盘,然后你会收到返回的ack通过MQMessage,这就是所谓的confirm机制传递消息的高延迟。看看下图,一起来感受一下吧。5、高并发下如何传递消息才不会丢失。你可以考虑一下。在生产端高并发写入MQ的场景下,你会面临两个问题:1.每次向MQ写入消息,为了等待这个消息的ack,必须把消息保存到一个storage中。而且这个存储不建议是内存,因为高并发的消息非常多,每秒可能有几千条甚至几万条消息传递。如果消息的ack要等上百毫秒,可能会有内存溢出的风险。2、绝对不能通过同步写消息+等待ack的方式来投递消息,这样会造成同步阻塞,每次投递都要等待几百毫秒,导致投递性能和吞吐量大幅下降。针对这两个问题,相应的解决方案其实已经呼之欲出了。首先,用于暂存unacked消息的存储需要承载高并发写入,我们不需要进行任何复杂的计算操作。这种存储的首选肯定不是MySQL之类的数据库,而是推荐kv存储。kv存储承载高并发能力非常强,kv运行性能非常高。其次,消息投递后等待ack的过程必须是异步的,即类似上面的代码已经给出了初步的异步回调方法。消息投递后,投递线程才真正返回。至于每条消息的异步回调,是通过在channel上注册一个confirm监听器来实现的。收到消息ack后,从kv存储中删除这条临时消息;收到消息nack后,从kv存储中提取消息,重新投递;也可以自己对kv存储中的消息进行监控,如果一定时间没有收到ack,就会主动重发消息。大家看下图,一起来体验下:6、消息中间件全链路100%丢包能不能实现?至此,我们已经分析了生产端和消费端结合RabbitMQ等中间件如何保证消息不丢失的相关技术方案。事实上,建筑思想是普遍的。无论使用哪种MQ中间件,其提供的功能都不尽相同,但需要考虑以下几点:生产端如何保证传递的消息不丢失:消息中途丢失,或者它由于MQ内存中的停机时间而丢失。这个时候,基于MQ的功能如何保证消息不会丢失呢?MQ本身如何保证消息不丢失:至少MQ需要有一种将消息持久化到磁盘的机制。消费端如何保证收到的消息不丢失:如果处理到一半,消费端宕机,消息丢失,这时候怎么办?目前,我们已经初步以RabbitMQ为例,从前到后分析了一整套技术方案的原理、设计和实现。但是这个时候,真的能做到100%数据丢失吗?恐怕不一定,我们考虑一个特殊的场景。生产端将消息投递给MQ,持久化到磁盘,并返回ack给生产端。但是此时MQ还没有将消息投递给消费者。结果,MQ部署的机器突然死机,磁盘不知何故损坏,直接导致MQ持久化到磁盘的数据在物理层面丢失。不要认为这是一个笑话。如果你关注行业新闻,这种磁盘损坏导致数据丢失的情况确实存在。那么即使此时重启MQ,磁盘上的数据也丢失了,数据还是丢失了吗?你说,我可以利用MQ的集群机制,做一份数据的多份副本。比如后面我们会为大家分析RabbitMQ的镜像集群机制,确实可以做到数据的多副本。但是即使数据有多个副本,也一定能够保证100%的数据不会丢失吧?比如你的机房突然地震了,机房里所有的机器都不见了。所有数据仍然丢失了吗?我这么说,并不是要提出争论。但是要告诉大家,技术是100%的理论预期。应该说我们是在朝着100%的方向努力,但是理论上不可能完全保证100%,也许99.9999%的可能数据不会丢失,但是还是有千万分之一的概率会迷路了。当然,从实际情况来看,做到这个程度也是可以的,其实基本数据是不会丢失的。