使用消息队列是为了能高性能的将消息从publisher处传递到consumer处,但是在传递过程中可能会出现一些问题导致消息没有被正常消费导致消息丢失。
在RabbitM中,消息从publisher发出到达交换机,在由交换机路由到消息队列中,最后到达消费者,当消费者接收消息后进行处理,如果此时消费者出现异常,就会不断的requeue(重新进入队列)再次发送给消费者,陷入循环,如果消息量过大,还会给Rabbitmq带来不必要的压力,因此,我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,即直接在本地进行重试操作,而不是无限制的requeue到mq队列,在application.yml中加入以下配置
spring:
rabbitmq:
listener:
simple:
prefetch: 1
retry:
enabled: true //开启消费者retry重试机制
max-attempts: 3 //重试最大次数
initial-interval: 1000 //第一次失败后等待时长
multiplier: 3.0 //每次失败后等待时长的翻倍数
stateless: true //true无状态;false有状态。如果业务中包含事务,这里改为false
如果重试后消息仍然处理失败,默认情况下那么消费者给publisher返回reject(拒绝),这时消息会从队列中被删去,消息也就发送失败了,当然我们也可也通过实现MessageRecoverer接口来实现其他操作
• RejectAndDontRequeueRecoverer :重试耗尽后,直接 reject ,丢弃消息。默认就是这种方式• ImmediateRequeueMessageRecoverer :重试耗尽后,返回 nack ,消息重新入队,之后该消息不被发送,等待人工处理• RepublishMessageRecoverer :重试耗尽后,将失败消息投递到指定的交换机
在publisher中定义一个配置类,用进行处理失败的消息统一处理,在这里我们实现RepublishMessageRecoverer,
@Configuration
public class ErrorConfig {
//创建一个专门处理失败消息的交换机error.direct
@Bean
public DirectExchange errorExchange()
{
return new DirectExchange("error.direct");
}
//创建一个存储失败消息的队列error.queue
@Bean
public Queue errorQueue()
{
return new Queue("error.queue",true); //设置队列持久化
}
//将error.direct和error.queue绑定起来,并指定对应的routingkey:error
@Bean
public Binding errorBind()
{
return BindingBuilder.bind(errorExchange()).to(errorExchange()).with("error");
}
// 实现MessageRecover接口的子类RepublishMessageBulider
@Bean
public MessageRecoverer republishMessageBuilder(RabbitTemplate rabbitTemplate)
{
return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
}
}
这样,在消息处理抛出异常后,在本地进行重试结束后,消息就不会丢失了,而是会被发送到error.queue队列中去,我们就可以进行人工的干预了,避免因产生异常导致消息丢失或者RabbitMq压力增大