RabbitMQ 之消息的可靠性消费

2,548 阅读4分钟

保障消息的可靠性消费主要有以下两个方面到内容

  • 消息在被消费端正确消费之前,不能删除
  • 消息在被消费端正确消费之后,必须要删除,否则消息会被重复消费

什么叫正确消费

消费端 消费消息可以简单看成两个过程

  • 接收消息
  • 消费消息

接收到消息后,是不能当作正确消费的,只有当消息被业务处理完成之后,才能看作正确消费。注意,如果业务处理过程中程序奔溃、异常,也不能看作正确消费

消息消费发生在消费端,RabbitMQ 怎么知道消息有没有正确消费呢?

答案是通过 RabbitMQ 提供消息确认机制(message acknowledgment)。

消息确认机制

消费端在声明队列时,可以指定noAck参数,当noAck=true时,消费端收到消息后会自动返回 ack 。当noAck=false时,消费端收到消息后需要显式调用basicAck,返回确认。

RabbitMQ 会一直持有消息直到消费者返回 ack 为止,前提是没有断开链接(这样设计的原因是,RabbitMQ 允许一个消息被消费很长时间),一但链接断开了,RabbitMQ 会认为消息消费失败,然后将消息投递给下一个消费者。

RabbitMQ 收到 ack信号后会从内存(和磁盘,如果是持久化消息的话)中移去消息

在 spring 中的使用

在 spring 中使用消息确认机制非常简单,首先要配置 ack 的方式

application.properties

# 签收方式 auth:自动签收 manual:手动签收  NONE:不签收 推荐手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual

如果不配置,默认为自动签收,这种模式下,只要收到消息就 ack 不管是否正确消费。这种模式显然不是我们想要的,所以一般需要配置为手动签收

正确消费后,手动 ack

// todo 正确消费消息
//手动 ack
channel.basicAck((Long)headers.get(AmqpHeaders.DELIVERY_TAG),false);

重复消费

上面说到 一但消费端和 RabbitMQ 之间的链接断开,RabbitMQ 会认为消息消费失败,然后将消息投递给下一个消费者。

但是,消息真的消费失败了吗?不一定。举个例子,消息消费成功后,刚想手动 ack ,突然服务崩溃了。这个时候链接虽然断开了,但是消息已经消费成功了,然后 RabbitMQ 以为消息消费失败,又把消息投递给下一个消费者。这样一来同一条消息就被消费了两次。

重复消费就不可避免

后果

根据业务的不同,重复消费的后果的严重性也不同。

比如支付业务,消息发送端投递了一个支付消息给 RabbitMQ ,RabbitMQ 将支付消息发送给力 消费者a,消费者收到消息后进行支付操作,支持完成之后,刚想手动 ack ,突然服务崩溃了,RabbitMQ 有将消息投递给了消费者B,消费者B又进行支付操作。

这样以来就支付了两次,要是真出现这种问题,哪个用户敢用这样的产品。

解决办法

重复消费的解决办法有两种

  • 消息去重
  • 幂等处理

当然,如果一个消息被消费多次也不会产生严重后果也可以选择不解决重复消费都问题

消息去重

消息去重是在消息消费之前,先查看是否消费过这个消息。

通常做法是获取业务消息中的业务id,比如支付业务发来的支付消息,里面一般都包含支付id,这个id在支付业务系统中是唯一的。

处理完消息之后,我们将这个支付id存下来(比如存到redis中),下次再有支付消息过来,我们取出id 一查找就知道之前有没有消费过。

这种做法有很多缺点,比如需要消息中包含唯一业务id(有的消息可能没有)、每个消费过的id都要存下来,而且还不能删,时间一长会积攒很多等等。

当然你也可以选择其他消息去重方案,如果没有合适的方案也可以选择对消费消息做幂等处理

幂等处理

所谓的对消费消息做幂等处理,指定是一个消息不管被重复消费多少次,结果都相当于只消费了一次。

这个就需要根据不同都业务进行巧妙都设计了,没有统一都答案。

相关文章

RabbitMQ 之消息的可靠性投递

RabbitMQ 持久化