RabbitMQ 之消息的可靠性消费

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

  • 消息在被消费端正确消费以前,不能删除
  • 消息在被消费端正确消费以后,必需要删除,不然消息会被重复消费

什么叫正确消费

消费端 消费消息能够简单当作两个过程spring

  • 接收消息
  • 消费消息

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

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

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

消息确认机制

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

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

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

在 spring 中的使用

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

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 持久化

相关文章
相关标签/搜索