高并发的场景下,如何保证生产者投递到消息中间件的消息不丢失
生产端如何保证消息不丢失的问题
在消息的生产端,如果投递的消息出去在网络传输过程中丢失,或者在RabbitMQ内存中,还没有写入磁盘的时候,发生宕机,都会导致生产端,投递到MQ的数据丢失。
保证消息不丢失的confirm机制
在生产端,首先要开启一个confirm机制, 接着投递到队列中的消息,如果MQ一旦将消息持久化到磁盘后,就必须要回传一个confirm消息给生产端。
这样的话,如果生产端的服务接收到这个confirm消息,数据就已经持久化到磁盘了。
否则,如果美云接收到confirm消息,那么就说明这条消息可能半路丢失了,此时你就可以重新投递消息到MQ去,确保消息不丢失。
MQ回传ack给生产端的时候,会带上delivery tag。这样你就知道具体对应着哪一次消息投递了,可以删除了这条消息。
此外,如果RabbitMQ接收到一条消息之后,结果内部出现错误,无法处理这条消息,那么他会回传一个nack给生产端。或者另外一种情况,消息队列很久都没有返回ack/unack,那么可能是极端意外的情况发生了,数据也丢了,你可以重新投递消息到MQ去。
confirm机制的高延迟性
一旦启用confirm机制投递消息到MQ之后,MQ是不保证什么时候会给你返回一个ack或者unack的。
因为RabbitMQ自己内部将消息持久化到磁盘,本身就是通过异步批量的方式来进行的,正常情况下,你投递的消息都会先驻留内存里,然后过了几百毫秒之后,再一次性批量的把多条消息持久化到磁盘里去。
这样做是为了兼顾高并发写入的时候,吞吐量和性能的,因为你要来一条消息,你就写入,那么性能会很差。
正因为这样,你开启confirm之后,你接收到MQ回传过来的ack可能会有几百毫秒的延迟。
高并发下如何投递消息才能不丢失
大家可以考虑一下,在生产端,高并发写入MQ,你会面临两个问题:
1、每次写一条数据,为了等待这条消息的ack,必须把消息保存到一个存储里。
这个存储不建议是内存,因为高并发的消息是很多,每秒可能都几千甚至上万的消息投递,消息的ack要等几百毫秒,放在内存的话,可能会有内存溢出的风险。
2、绝对不能以同步写消息+等待ack的方式来投递
那样每投递一次,就会阻塞等几百毫秒,会导致投递性能和吞吐量的大幅度下降。