如何确保RabbitMQ消息的可靠性?
logging:
pattern:
dateformat: HH:mm:ss:SSS
level:
cn.itcast: debug # Debug Info Warn Error Fatal
spring:
rabbitmq:
host: 192.168.23.130 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast
password: 123321
virtual-host: /
publisher-confirm-type: correlated #ConfirmCallback 生产者消费确认到交换机
publisher-returns: true #ConfirmCallback ReturnCallback 到队列
template:
mandatory: true
ApplicationContextAware ->bean工厂通知->拿到rabbitTemplate
@Slf4j
@Configuration
//生产者消息确认,确认信心到达队列
public class CommonConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获取RabbitTemplate对象
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
//配置ReturnCallback
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
//判断是否是延迟消息
if(message.getMessageProperties().getReceivedDelay()>0){
return;
}
//失败时才会回调
//处理:记录日志
log.error("消息发送到队列失败,响应码:{},失败原因:{},交换机:{},路由key:{},消息:{}",replyCode,replyText,exchange,routingKey,message);
//可以得到所有的错误信息,有需要的话,可以选择重发信息
});
}
}
@Test
//生产者消息确认,确认信息到达交换机
public void testSendMessage2SimpleQueue1() throws InterruptedException {
String routingKey = "red";
// 1.消息体
String message = "hello, spring amqp!";
// 2.全局唯一的消息ID,需要封装到CorrelationData中
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 3.添加callback
correlationData.getFuture().addCallback(confirm -> {
if (confirm.isAck()){
//ASC
log.debug("消息发送到交换机成功:ID:{}",correlationData.getId());
}else {
//nASC
log.debug("消息发送到交换机失败:ID:{},原因:{}",correlationData.getId(),confirm.getReason());
}
}, throwable -> {
log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),throwable.getMessage());
});
// 4.发送消息
rabbitTemplate.convertAndSend("exchange.direct", routingKey, message,correlationData);
// 休眠一会儿,等待ack回执
Thread.sleep(2000);
}
@Bean
public DirectExchange simpleExchange(){
// 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
return new DirectExchange("simple.direct", true, false);
}
@RabbitListener
value = @Queue(name = "dl.ttl.queue", durable = "true"), 持久化
exchange = @Exchange(name = "dl.ttl.direct",durable = "true"), //死信交换机
@Bean
public Queue simpleQueue(){
// 使用QueueBuilder构建队列,durable就是持久化的
return QueueBuilder.durable("simple.queue").build();
}
RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。
而RabbitMQ是通过消费者回执来确认消费者是否成功处理消息的:消费者获取消息后,应该向RabbitMQ发送ACK回执,表明自己已经处理消息。
设想这样的场景:
这样,消息就丢失了。因此消费者返回ACK的时机非常重要。
而SpringAMQP则允许配置三种确认模式:
由此可知:
一般,我们都是使用默认的auto即可
@Configuration
public class ExchangeErrorQueueConfig {
private final String ExchangeName ="error.direct";
private final String QueueName ="error.queue";
private final String RoutingKey ="error";
@Bean
//定义错误交换机
public DirectExchange errorMessageExchange(){
return new DirectExchange(ExchangeName);
}
//定义错误处理队列
@Bean
public Queue errorQueue(){
return new Queue(QueueName);
}
//将交换机和队列绑定
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with(RoutingKey);
}
//定义一个RepublishMessageRecoverer,关联队列和交换机
@Bean
public RepublishMessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate,ExchangeName,RoutingKey);
}
}
消费者两种模式配置
logging:
pattern:
dateformat: HH:mm:ss:SSS
level:
cn.itcast: debug
spring:
rabbitmq:
host: 192.168.23.130 # rabbitMQ的ip地址
port: 5672 # 端口
username: itcast
password: 123321
virtual-host: /
listener:
simple:
prefetch: 1
#acknowledge-mode: none # 关闭ack 消息处理抛异常时,消息依然被RabbitMQ删除
acknowledge-mode: auto # ack 自动返回结果
retry:
enabled: true # 开启消费者失败重试 在消费者本地重试,不会返回队列
initial-interval: 1000 # 初识的失败等待时长为1秒
multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 3 # 最大重试次数
stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false