RabbitMQ消息可靠性保障
RabbitMQ中其实没有对消息延迟进行实现,但是我们可以通过TTL以及死信路由来实现消息延迟
1.生产者保证
1.失败通知
生产者发送消息到broker时,要保证消息的可靠性,主要的方案有以下2种
- 失败通知
- 发送方确认
1.失败通知:如果出现消息无法投递到队列会出现失败通知,就可以启动失败通知,在原生编程中在发送消息时设置mandatory标志,即可开启故障检测模式
2.实现方式:
spring中的配置:
pring:
rabbitmq:
# 消息在未被队列收到的情况下返回
publisher-returns: true
java代码中需要发送者实现ReturnCallback接口方可实现失败通知:
public class sendToMessage implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
}
@Override
public void returnedMessage(ReturnedMessage returned) {
RabbitTemplate.ReturnCallback.super.returnedMessage(returned);
}
}
3.遇到的问题:如果消息正确路由到队列,则发布者不会受到任何通知,带来的问题是无法确保发布消息一定是成功的,因为路由到队列的消息可能会丢失
2.发送方确认
发送方确认是指生产者投递消息后,如果 Broker 接收到消息,则会给生产者一个应答,生产者进行接收应答,用来确认这条消息是否正常的发送到 Broker,这种方式也是消息可靠性投递的核心保障
- 消息通过交换机exchange被路由到队列queue
- 将消息发送到broker,即发送到exchage交换机
注意:发送发确认只有出现RabbitMQ内部错误无法投递才会出现发送发确认失败
1 不可路由:当前消息到达交换器后对于发送者确认是成功的
首先当RabbitMQ交换器不可路由时,消息也根本不会投递到队列中,所以这里只管到交换器的路径,当消息成功送到交换器后,就会进行确认操作
另外在这过程中,生产者收到了确认消息后,那么因为消息无法路由,所以该消息也是无效的,无法投递到队列,所以一般情况下这里会结合失败通知来一同使用,这里一般会进行设置mandatory模式,失败则会调用addReturnListener监听器来进行处理
2.可以路由;只要消息能够到达队列即可进行确认,一般是RabbitMQ发生内部错误才会出现失败
以路由的消息,要等到消息被投递到所有匹配的队列之后,broker会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了。
如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号
3.实现方式
spring中的配置:
spring:
rabbitmq:
# 开启消息确认机制
publisher-confirm-type: correlated
java代码中需要发送者实现ConfirmCallback接口方可实现失败通知
public class sendToMessage implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
}
}
3. Broker丢失消息
开启RabbitMQ的持久化,也即消息写入后会持久化到磁盘,此时即使mq挂掉了,重启之后也会自动读取之前存储的额数据
1.持久化队列
@Bean
public Queue queue(){
return new Queue(queueName,true);
}
2.持久化交换器
@Bean
DirectExchange directExchange() {
return new DirectExchange(exchangeName,true,false);
}
3.发送持久化消息
发送消息时,设置消息的deliveryMode=2
注意:如果使用SpringBoot的话,发送消息时自动设置deliveryMode=2,不需要人工再去设置
2.消费方消息可靠性(消费者手动确认)
1.RabbitMQ默认是自动ack的,需要将其修改为手动ack,也即自己的程序确定消息已经处理完成后,手动提交ack,此时如果再遇到消息未处理进程就挂掉的情况,由于没有提交ack,RabbitMQ就不会删除这条消息,而是会把这条消息发送给其他消费者处理,但是消息是不会丢的
2.spring配置文件
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 手动ack
3.参数介绍
acknowledge-mode: manual就表示开启手动ack,该配置项的其他两个值分别是none和auto
- auto:消费者根据程序执行正常或者抛出异常来决定是提交ack或者nack,不要把none和auto搞混了
- manual: 手动ack,用户必须手动提交ack或者nack
- none: 没有ack机制
4.消费者实现
public class sendToMessage {
@RabbitHandler
public void processOrder(Message massage, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
channel.basicAck(tag, false);
}
}
3.业务可靠性分析
1.消息丢失:在这个业务场景中,用户发起打车请求,如果用户消息丢失,对整体业务是没有任何影响的,用户可以再次发起打车操作,这个消息丢失问题概率很低,可以进行简单化设计,如果出现发送失败只需要回退redis中的操作即可。
2.幂等性校验:因为使用了延时队列,对于这个业务来说是不需要进行幂等性校验的,因为第一次超时时如果存在redis用户排名的key就会被删除,下一次redis没有的值在删除一次,这种操作是幂等的,所以不需要考虑幂等性
3.数据回滚:虽然无需做到消息完全不丢失以及消息的幂等性,但是需要考虑如果出现问题,需要将插入Redis的的key值回滚掉,防止影响业务正常判断