前言
我们在使用 RabbitMQ 的时候,因为网络问题等种种原因,消息很可能在发送过程中发生各种问题,例如消息丢失,重复消费。因此,我们使用 RabbitMQ 时要采取一些措施来确保消息的可靠性,防止数据丢失。
confim机制(保证发送可靠)
RabbitMQ 的事务:事务可以保证消息100%传递,可以通过事务的回滚去记录日志,后面定时再次发送当前消息。事务的操作,效率太低,加了事务操作后,比平时的操作效率至少要慢100倍。
RabbitMQ 除了事务,还提供了 Confirm 的确认机制,这个效率比事务高很多。
Return机制(保证发送可靠)
Confirm 只能保证消息到达 exchange,无法保证消息可以被 exchange 分发到指定 queue。 而且 exchange 是不能持久化消息的,queue 是可以持久化消息。 因此可以采用Return机制来监听消息是否从 exchange 送到了指定的 queue 中。
手动Ack(保证接收可靠)
在默认情况下 RabbitMQ 是自动ACK机制,这就意味着 RabbitMQ 会在消息发送完毕后,自动帮我们去ACK,然后删除消息的信息。
这样一来就存在这样一个问题: 如果消费者处理消息需要较长时间,RabbitMQ 可能会删除掉一些消息,因此,最好的做法是消费端处理完之后手动去确认。
操作方法如下:
- 在消费方application.yml文件添加下面配置,改为手动应答机制
spring:
rabbitmq:
host: 192.168.200.129
port: 5672
virtual-host: /test
username: test
password: test
listener:
simple:
acknowledge-mode: manual
- 手动ack
@Component
public class Consumer {
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
System.out.println("接收到消息:" + msg);
try {
int i = 1 / 0;
/**
* 消费者发起成功通知
* 第一个参数: DeliveryTag,消息的唯一标识 channel+消息编号
* 第二个参数:是否开启批量处理 false:不开启批量
* 举个栗子: 假设我先发送三条消息deliveryTag分别是5、6、7,可它们都没有被确认,
* 当我发第四条消息此时deliveryTag为8,multiple设置为 true,会将5、6、7、8的消息全部进行确认。
*/
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
/**
* 返回失败通知
* 第一个参数: DeliveryTag,消息的唯一标识 channel+消息编号
* 第二个boolean true所有消费者都会拒绝这个消息,false代表只有当前消费者拒绝
* 第三个boolean true消息接收失败重新回到原有队列中
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
}
}
}
避免消息重复消费
重复消费消息,会对非幂等行操作造成问题
重复消费消息的原因是,消费者没有给RabbitMQ一个ack
为了解决消息重复消费的问题,可以采用Redis,在消费者消费消息之前,先将消息的id放到Redis中, ‘id-0’ 代表正在执行业务, ‘id-1’ 代表执行业务成功。
然后使用ack给RabbitMQ返回消息, 如果RabbitMQ ack失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取他的值,如果是0,当前消费者就什么都不做,如果是1,直接ack。
极端情况:第一个消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间。
备注: java中的方法叫做setIfAbsent, redis中的命令叫做setnx 作用: 如果为空就set值,并返回(1, true) 如果存在(不为空)不进行操作,并返回(0, false)