一、Rabbitmq生产者-----》交换机-----routingkey----》队列《-------消费者,这中间有几个地方存在因网络会服务器宕机原因导致消息丢失,如何保证消息可靠,至关重要!
- exchange、queue和message是持久化的
- confirm和return机制
- 手动ack确保消息被消费
- 当消息积压时,加入死信队列
详解和实战:
1、首先必须设置exchange和queue的属性durable为true:
@Bean
public TopicExchange topicExchange() {
TopicExchange exchange = ExchangeBuilder.topicExchange(TOPIC_PLACE_ORDER_EXCHANGE).durable(true).build();
//rabbitAdmin.declareExchange(exchange);
return exchange;
}
@Bean
public Queue userQueue() {
return QueueBuilder.durable(USER_QUEUE).build();
}
2、消息一定要送达exchange,开启confirm机制:
如何保证消息一定能从生产者发送到exchange,请参考 CSDN 中Publisher Confirm通讯模式的原生实现,下面介绍集成Springboot的实现:
(1)配置文件开启消息确认:
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application:
name: example
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated #开启消息确认
从源码中可以看出,消息确认类型有三种:
SIMPLE:代表单条或批量消息同步确认
CORRELATED: 与回调函数一起使用,实现异步消息确认
NONE:不确认
(2)编写回调函数:
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if(!ack){
System.out.println("====消息发送失败,重试!====");
}
});
通过查看源码:setConfirmCallback需要传递一个ConfirmCallback对象实例,使用lamda表达式实现即可
3、开启return机制:消息从exchang通过routingkey将消息路由到queue,也可能会发生消息的丢失,Rabbitmq使用Return机制来确保消息能从exchange路由到queue:
(1)编写配置文件,开启return机制:
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application:
name: example
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated #开启消息确认
publisher-returns: on # 可以写on/off,或者true/false
(2)编写回调函数:
//只要调用了该方法,说明exchange在路由消息到queue时,失败了
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
System.out.println("===消息发送失败,进行补救====");
});
3、服务器重启,queue里面的消息不会丢失
注意:在channel.queueDeclear()方法中的durable参数,指的当Rabbitmq重启的时候,queue不会丢失,而不是queue里面的消息不会丢失,当前我们需要讨论的是queue里面的消息,不丢失:
如何设置发送的消息持久化:通过MessagePostProcessor实例传递一个消息持久化的标识:
//配置消息持久化标识
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
};
//只会匹配第二个队列,#是通配符,可以有多个.
rabbitTemplate.convertAndSend(SpringbootRabbitmqConstant.TOPIC_EXCHANGE, "111.222.aaa.bbb.ccc", msg, messagePostProcessor);
4、消费者获取队列消息不会丢失(手动ack)
(1)编写配置文件
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application:
name: example
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated #开启消息确认
publisher-returns: on # 可以写on/off,或者true/false
listener:
direct:
acknowledge-mode: manual # 设置手动ack,none表示不ack,auto表示自动ack
(2)在消费者@RabbitListener中,执行完成后手动ack:
@RabbitListener(queues = SpringbootRabbitmqConstant.FANOUT_QUEUE + "1", ackMode = "AUTO")
public void recoverMessage1(String msg, Message message, Channel channel) throws IOException {
long deliveryTag = 0;
try {
byte[] body = message.getBody();
msg = new String(body, StandardCharsets.UTF_8);
deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("recoverMessage1============" + msg + "deliveryTag:" + deliveryTag);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//手动ack
channel.basicAck(deliveryTag, false);
}
}
5、死信队列:
当队列中的消息达到预设阈值时,再发过来的消息会被删除,导致消息丢失;
引入死信队列,将消息发送到死信队列中,保证消息不丢失;
6、搭建Rabbitmq高可用,配置镜像队列进行备份,除非所有节点都挂掉才会消息丢失,虽然不能保证100%可靠,但比不配置,可靠性高很多,一般生产环境建议使用