一个完美的消息队列,应该做到消息的“不重不漏”,这里面包含了4重语义:
消息不会重复存储;
消息不会重复消费;
消息不会丢失存储;
消息不会丢失消费。
重复存储
发送者发送一个消息之后,服务器返回超时了。那请问,这条消息是存储成功了,还是没有呢?
所以生产者必须持有一个消息表,记录消息的发送状态,对于超时未确认的消息要再查询确认,否则无法保证不重复发送重复存储。
一般重复发送问题是不去解决的,都是在消息端做判重表来解决重复问题。
重复消费
一种是生产者就是重复发的消息,所以消息重了。
另一种是消息者消息完消息还没向MQ确认挂了,再重启还是有这个消息。
一般消费者需要根据业务ID做判重表,消息过的就不再消费。
丢失存储
如果MQ支持ACK,那生产者在收到MQ的确认后一般认为存储成功。
但也有极少情况会丢失消息,最极端情况:
操作系统本身是有page cache的。即使我们用无缓冲的io,消息也不会立即落到磁盘上,而是在操作系统的page cache里面。操作系统会控制page cache里面的内容,什么时候写回到磁盘。在应用层,对应的就是fsync函数。我们可以指定每条消息都调用一次fsync存盘,但这会较低性能,也增大了磁盘IO。
一般是不会一条消息做一次磁盘IO的,这样性能太差,所以想完全不丢失消息生产者可以持有消息表,最后和消费者对账。
对账是因为生产者收到消息确认,但消息丢失了,需要和消费者对账才能找出丢失的消息。
丢失消费
消息被消费,但还没被确认消费者就挂了,所以消息还是在MQ中。
需要判重表解决,在消费者重启后消息被判重过滤掉。