消息可靠性问题
在消息队列,任何一个环节出问题都会导致消息的不可靠(消息丢失),如何确保消息的可靠性呢,需要考虑到其中的每个角色,生产者可靠性、MQ可靠性、消费者可靠性。
生产者可靠性
1、生产者重试
生产者发送消息时,出现了网络故障,导致与MQ的连接中断,所以需要设置重试机制进行多次重试,在SpringAMQP的重试机制是阻塞式重试,会出现当前线程阻塞问题,可能会导致性能下降。
2、生产者确认
RabbitMQ有两种消息确认机制
- Publisher Confirm
用于确认消息是否成功到达 Exchange(交换器),但不关心消息是否路由到队列。属于生产者与RabbitMQ 服务端之间的确认机制。解决的是 “消息是否成功发出” 的问题。 - Publisher Return
用于确认消息是否从 Exchange 成功路由到 Queue(队列)。当消息无法路由到任何队列时,RabbitMQ 会通过 ReturnCallback 将消息返回给生产者。解决的是 “消息是否被队列接收” 的问题。
实现: - Publisher Confirm使用
1)引入springamqp依赖之后,通过配置文件开启
publisher-confirm-type有三种模式可选: - none:关闭confirm机制
- simple:同步阻塞等待MQ的回执
- correlated:MQ异步回调返回回执
spring:
rabbitmq:
publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
publisher-returns: true # 开启publisher return机制
2)定义ConfirmCallback
在调用RabbitTemplate中的convertAndSend方法时,多传递一个参数:
@Test
public void testConfimCallback(){
//1、创建CorrelationData
CorrelationData correlationData = new CorrelationData();
correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
//1、异常处理逻辑
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
//接收回执信息ack或者nack
if(result.isAck()){
//生产者发送消息成功
}
else{
//!result.isAck()即nack
//生产者发送消息失败
result.getReason();//失败原因
}
}
});
rabbitTemplate.convertAndSend("交换机","路由","message",correlationData);
}
- Publisher Return使用
@Configuration
public class MQConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
//逻辑处理
}
});
}
}
MQ可靠性
MQ的消息默认都是保存内存当中,会存在两个问题:1、重启消息丢失;2、内存空间有限,消息积压导致MQ阻塞。
1、数据持久化
- 交换机持久化
- 队列持久化
- 消息持久化
2、LazyQueue
消费者可靠性
1、确认机制
SpringAMQP帮我们实现了消息确认。并允许我们通过配置文件设置ACK处理方式,有三种模式:
- none:不处理。即消息投递给消费者后立刻ack,消息会立刻从MQ删除。非常不安全,不建议使用
- manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务入侵,但更灵活
- auto:自动模式。SpringAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回ack. 当业务出现异常时,根据异常判断返回不同结果:
- 如果是业务异常,会自动返回nack;
- 如果是消息处理或校验异常,自动返回reject;
2、重试机制
spring: rabbitmq: listener: simple: retry: enabled: true # 开启消费者失败重试 initial-interval: 1000ms # 初识的失败等待时长为1秒 multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval max-attempts: 3 # 最大重试次数 stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
3、失败处理策略
@Configuration
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry.enabled",havingValue = "true")
public class ErrorConfigMQ {
@Bean
public DirectExchange errorExchange(){
return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){
return new Queue("error.queue");
}
@Bean
public Binding errorBinding(DirectExchange errorExchange, Queue errorQueue){
return BindingBuilder.bind(errorQueue).to(errorExchange).with("error");
}
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
}
}
4、业务幂等性
何为幂等性?
幂等是一个数学概念,用函数表达式来描述是这样的:f(x) = f(f(x)),例如求绝对值函数。
在程序开发中,则是指同一个业务,执行一次或多次对业务状态的影响是一致的。
- 唯一消息ID
- 业务判断
总结
通过保证生产者、MQ、消费者的可靠性,来保证消息的可靠性。