消息从生产者发送到exchange,再到queue,再到消费者,有哪些导致消息丢失的可能性?
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息到达MQ后,会返回一个结果给发送者,表示消息是否处理成功。结果有两种请求:
# 开启 publisher-confirm
# simple: 同步等待回调
# correlated: 异步回调,定义ConfirmCallback,MQ返回结果会回调这个ConfirmCallback
publisher-confirm-type: correlated
# 开启 publisher-return 功能,同样是基于callback机制,不过是定义ReturnCallback
publisher-returns: true
# 定义消息路由失败时的策略。true: 则回调ReturnCallback; false: 则直接丢弃消息
template:
mandatory: true
每个RabbitTemplate 只能配置一个ReturnCallback(全局共用)
@Configuration
@Slf4j
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setReturnCallback((message, i, s, s1, s2) -> {
log.info("ReturnCallback...");
log.info("消息: {}", message);
log.info("应答码: {}", i);
log.info("失败原因: {}", s);
log.info("路由key: {}", s1);
log.info("消息: {}", s2);
});
}
}
// 消息体
String message = "hello rabbitmq";
// 消息id 消息配上唯一的id,为了区分消息
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 添加 callback
correlationData.getFuture().addCallback(confirm -> {
// 成功回调
if (confirm.isAck()) {
// ack
log.info("消息成功投递到交换机! {}", correlationData.getId());
} else {
// nack
log.info("消息投递到交换机失败! {}", correlationData.getId());
}
}, throwable -> {
// 失败回调
log.info("消息发送失败: {}", throwable.getMessage());
});
// 发送消息
rabbitTemplate.convertAndSend("amq.exchange", "simple", message, correlationData);
MQ是默认是内存存储消息,开启持久化功能可以确保缓存在MQ中的消息不丢失
@Bean
public DirectExchange durableExchange() {
// 三个参数: 交换机名称,是否持久化,当没有queue与其绑定时自动删除
return new DirectExchange("durable.exchange", true, false);
}
@Bean
public Queue simpleQueue() {
return QueueBuilder.durable("durable.queue").build();
}
Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT) //持久化
.build();
RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。
而SpringAMQP允许配置三种确认方式
spring:
rabbitmq:
listener:
simple:
prefetch: 1
acknowledge-mode: auto
当消费者出现异常后,消息会不断 requeue (重新入队) 到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息飙升,带来不必要的压力
spring:
rabbitmq:
listener:
simple:
prefetch: 1
retry:
# 开启消费者重试
enabled: true
# 初次失败等待时长为1秒
initial-interval: 1000
# 下次失败的等待时长倍数,下次等待时长 = multiplier * initial-interval
multiplier: 1
# 最大重试次数
max-attempts: 3
# true无状态;false有状态。如果业务中包含事务,这里改为false
stateless: true
在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要MessageRecover接口来处理,它包含三种不同的实现
1 定义失败消息的交换机、队列及其绑定关系
@Bean
public DirectExchange errorExchange() {
return new DirectExchange("error.exchange");
}
@Bean
public Queue errorQueue() {
return new Queue("error.queue", true);
}
@Bean
public Binding errorBinding(){
return BindingBuilder.bind(errorQueue()).to(errorExchange()).with("error");
}
2 定义RepublishMessageRecoverer
@Bean
public RepublishMessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error");
}
当一个队列中的消息满足下列情况之一时,可以成为死信
如果一个队列配置了 dead-letter-exchange 属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)
TTL,也就是Time-To-Live。如果一个队列中的消息TTL结束仍未消费,则会变成死信,ttl超时会分为两种情况:
接收方
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "dl.queue", durable = "true"),
exchange = @Exchange(name="dl.direct"),
key = "dl"
))
public void listenDlQueue(String msg){
log.info("接收到 dl。queue的延迟消息:{}", msg);
}
发送方:
@Bean
public DirectExchange ttlExchange() {
return new DirectExchange("ttl.direct");
}
@Bean
public Queue ttlQueue() {
return QueueBuilder.durable("ttl.queue") // 指定队列名称,并持久化
.ttl(10000) // 设置队列的超时时间, 10秒
.deadLetterExchange("dl.direct") // 指定死信交换机
.deadLetterRoutingKey("dl") // 指定死信 RoutingKey
.build();
}
@Bean
public Binding ttlBinding() {
return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
}
RabbitTemplate rabbitTemplate = run.getBean(RabbitTemplate.class);
Message msg = MessageBuilder.withBody("ttl message".getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT) //持久化
.setExpiration("5000") // 消息失效时间
.build();
rabbitTemplate.convertAndSend("ttl.direct","ttl", msg);
利用 TTL 结合死信交换机,我们实现了消息发出后,消费者延迟收到消息的效果。这种消息模式被称为延迟队列(Delay Queue)模式。
延迟丢列的使用场景包括:
延迟队列插件