「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
我们在使用消息队列时,很大概率会遇到消息丢失,消息重复消费的问题,今天我们就来一起探究一下消息丢失问题与消息重复消费问题产生的原因以及如何避免这些问题,以实现消息的可靠传递。
1 问题出现的原因
1.1 消息丢失问题出现的原因
-
生产方丢失原因:
- 发送过程出现异常
-
消息队列丢失原因:
- 未持久化消息,服务重启
- 持久化出现异常,未成功持久化
-
消费方丢失原因:
- 消费出现异常
1.2 重复消费问题出现的原因
正常情况下,消费者在消费消息完毕之后,会发送一个确认消息给消息队列,消息队列就知道该消息已经被消费了,就会将该消息从消息队列中删除;但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息,就会再次将消息分发给其他的消费者,从而出现消息的重复消费。
2 解决方案
2.1 消息丢失问题解决方案
-
解决生产方丢失:
从生产方丢失数据这个角度来看,RabbitMQ提供transaction和confifirm模式来确保生产者不丢消息;
-
事务transaction:
发送消息之前,开启事务(channel.txSelect()),然后发送消息,如果发送过程出现异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。
缺点:吞吐下降
-
确认模式confirm:
confirm模式更为常用,当信道进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ就会发送一个ACK给生产方,并且包含消息的唯一ID,这就使得生产方可以确认消息已经投递成功;如果失败,会收到Nack消息,可以在业务上进行重试操作。
-
-
解决消息队列丢失:
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。持久化配置可以和confifirm机制配合使用,这样就可以使消费方确认消息投递成功,且被持久化。
设置方式:
- 将队列的持久化标识durable设置为true,则代表是一个持久的队列
- 发送消息的时候将消息投递模式设置为deliveryMode=2
-
解决消费方丢失:
消费方丢失数据一般是因为采用了自动确认消息模式,需要改为手动确认消息。
消费方在收到消息之后,开始处理消息之前,会自动回复RabbitMQ已收到消息;如果这时处理消息失败,就会丢失该消息;应该处理消息成功后,手动回复确认消息。
2.2 重复消费问题解决方案
保证消费消息的唯一性,就算是多次传输,也不要让消息的多次消费带来影响,保证消息的幂等性。
对写入消息队列的数据做唯一标示(id),消费消息时,根据唯一标识判断是否消费过;消费一条消息就往数据库或者是缓存中插入一条数据,消费时先判断一下数据是否已经消费过了(存在数据库或是缓存中),若是就直接丢失,这样就可以确保消费了一条数据,从而保证了数据的正确性。
具体操作步骤:
- 消费者获取到消息后先根据id去查询db/redis是否存在该消息
- 如果不存在,则正常消费,消费完毕后写入db/redis
- 如果存在,则证明消息被消费过,直接丢弃