RabbitMQ的使用场景
- 异步发送消息(验证码、短信、邮件…)
- mysql、redis、es之间的数据同步
- 分布式事务
- 削峰填谷
- …
面试题:RabbitMQ如何保证消息不丢失
消息丢失原因
- 生产者发送的消息未到达交换机
- 交换机未把消息路由到队列
- mq服务器宕机,队列中的消息丢失
- 消费者服务宕机,未接收到消息
- …
发送端消息丢失解决方案
生产者确认机制
rabbitmq提供了publisher confirm机制来避免消息发送到mq过程中丢失。返回一个结果给生产者,表示消息是否接收成功。
消息接收失败后处理方案
- 回调方法即时发送
- 记录日志
- 保存到数据库表定时重发,发送成功后删除表数据
交换机持久化
@Bean
public DirectExchange directExchange(){
return new DirectExchange("myFanout.fanout",true,false);
}
队列持久化
@Bean
public Queue simpleQueue(){
return
// 使用QueueBuilder.durable创建的队列是持久化的
QueueBuilder.durable("simpleQueue").build();
}
消息持久化
mq默认是消息存储在内存中,开启持久化功能可以保证缓存在mq中的消息不丢失。
String message = "hello";
Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8)) // 设置消息体(将消息转换成byte)
.setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 将消息持久化
.build();
消费者确认机制
rabbitmq支持消费者确认机制,消费者处理消息后向mq发送ack回执,mq收到ack后将消息删除。springAMQP则支持三种确认方式。
- manual :手动ack,需要在业务代码执行完成后,手动调用api发送ack
- auto : 自动ack,有spring监听代码是否发生异常,没有异常则发送ack,有异常则发送nack。(推荐使用)
- none :关闭ack,mq假定消息者收到消息后就会处理成功,投递消息后将消息删除
我们可以利用Spring的retry机制,在消费者出现异常时利用本地重试,设置重试次数,当次数达到了以后,如果消息依然失败,将消息投递到异常交换机,交由人工处理。
rabbitmq消息重复消费怎么解决
rabbitmq消息重复消费产生原因:
- 网络波动
- 消费者服务宕机
解决方案:
- 给每条消息设置一个唯一的id标识(支付id、订单号、文章id…)
- 幂等方案【分布式锁、数据库锁(乐观锁、悲观锁)】
**面试回答:**嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了。
因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了。
rabbitmq死信交换机(延迟队列)有了解过吗
场景:超时订单、限时优惠、定时发布…
死信交换机
当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter) :
- 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false
- 消息是一个过期消息,超时无人消费
- 要投递的队列消息堆积满了,最早的消息可能成为死信
如果该队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机(Dead Letter Exchange,简称DLX)。
@Bean
public Queue ttlQueue(){
Map<String,Object> map = new HashMap<>();
//设置死信队列属性
map.put("x-dead-letter-exchange","order_exchange_delay");
map.put("x-dead-letter-routing-key","dlx.order.get");
map.put("x-message-ttl",5000);
return QueueBuilder.durable("dl.queue").withArguments(map).deadLetterExchange("dl.direct").build();
}
TTL
TTL,也就是Time-To-Live。如果一个队列中的消息TTL结束仍未消费,则会变为死信,TTL超时分为两种情况:
- 消息所在的队列设置了存活时间
- 消息本身设置了存活时间
如果这两种情况设置了TTL,那么以时间小的为准