1. 为何大家公司选择RabbitMQ做为消息中间件
在消息队列选型时,咱们调研了市场上比较经常使用ActiveMQ,RabbitMQ,RocketMQ,Kafka。java
- RabbitMQ相对成熟稳定,这是咱们选择它最主要的缘由。
- 社区比较活跃,有完善的资料能够参考。
- Rabbitmq的吞吐量能够达到万级,彻底知足咱们系统的要求。
- RabbitMQ是Erlang语言开发的,性能比较好。
- 有完善的可视化界面,方便查看。
2. 消息队列的优势和缺点有哪些
优势有:面试
- 异步处理 - 相比于传统的串行、并行方式,提升了系统吞吐量。
- 应用解耦 - 系统间经过消息通讯,不用关心其余系统的处理。
- 流量削锋 - 能够经过消息队列长度控制请求量;能够缓解短期内的高并发请求。
缺点有:redis
- 系统可用性下降
- 系统复杂度提升
3. RabbitMQ经常使用的工做模式有哪些
2.1 简单模型
- p:生成者
- C:消费者
- 红色部分:quene,消息队列
2.2 工做模型
这种模式下一条消息只能由一个消费者进行消费,默认状况下,每一个消费者是轮询消费的。spring
- p:生成者
- C一、C2:消费者
- 红色部分:quene,消息队列
2.3 发布订阅模型(fanout)
这种模型中生产者发送的消息全部消费者均可以消费。微信
- p:生成者
- X:交换机
- C一、C2:消费者
- 红色部分:quene,消息队列
2.4 路由模型(routing)
这种模型消费者发送的消息,不一样类型的消息能够由不一样的消费者去消费。网络
- p:生成者
- X:交换机,接收到生产者的消息后将消息投递给与routing key彻底匹配的队列
- C一、C2:消费者
- 红色部分:quene,消息队列
2.5 主题模型(topic)
这种模型和direct模型同样,都是能够根据routing key将消息路由到不一样的队列,只不过这种模型可让队列绑定routing key 的时候使用通配符。这种类型的routing key都是由一个或多个单词组成,多个单词之间用.
分割。并发
通配符介绍:app
*
:只匹配一个单词dom
#
:匹配一个或多个单词异步
4. 如何保证消息不丢失(如何保证消息的可靠性)
一条消息从生产到消费经历了三个阶段,分别是生产者,MQ和消费者,对于RabbitMQ来讲,消息的传递还涉及到交换机。所以RabbitMQ出现消息丢失的状况有四个
分别是
- 消息生产者没有成功将消息发送到MQ致使消息丢失
- 交换机未路由到消息队列致使消息丢失
- 消息在MQ中时,MQ发生宕机致使消息丢失
- 消费者消费消息时出现异常致使消息丢失
针对上面提到的四种状况,分别进行处理
- amqp协议提供了事务机制,在投递消息时开启事务,若是消息投递失败,则回滚事务,不多有人去使用事务。除了事务以外,RabbitMQ还提供了生产者确认机制(publisher confirm)。生产者将信道设置成confirm(确认)模式,一旦信道进入confirm模式,全部在该信道上面发布的消息都会被指派一个惟一的ID(从1开始),一旦消息被投递到全部匹配的队列以后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的惟一ID),这就使得生产者知晓消息已经正确到达了目的地了。
# 开启生产者确认机制, # 注意这里确认的是是否到达交换机 spring.rabbitmq.publisher-confirm-type=correlated
@RestController public class Producer { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("send") public void sendMessage(){ /** * 生产者确认消息 */ rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println(correlationData); System.out.println(ack); System.out.println(cause); } }); rabbitTemplate.convertAndSend("s","error","这是一条错误日志!!!"); } }
- 消息从交换机未能匹配到队列时将此条消息返回给生产者
spring.rabbitmq.publisher-returns=true
@RestController public class Producer { @Autowired private RabbitTemplate rabbitTemplate; @GetMapping("send") public void sendMessage(){ /** * 消息未达队列时返回该条消息 */ rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println(returnedMessage); } }); rabbitTemplate.convertAndSend("s","error","这是一条错误日志!!!"); } }
- 消息在交换机或队列中发生丢失,咱们只须要将交换机和队列进行持久化。
/** * 定义一个持久化的topic交换机 * durable 持久化 * @return */ @Bean public Exchange exchangeJavatrip(){ return ExchangeBuilder.topicExchange(EXCHANGE).durable(true).build(); } /** * 定义一个持久化的队列 * durable 持久化 * @return */ @Bean public Queue queueJavatrip(){ return QueueBuilder.durable(QUEUE).build(); }
- 消费者开启手动签收模式,消费完成后进行ack确认。
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@RabbitListener(queues = MqConfig.QUEUE) public void receive(String body, Message message, Channel channel) throws Exception{ long deliveryTag = message.getMessageProperties().getDeliveryTag(); System.out.println(deliveryTag); // 系统业务逻辑判断是否签收 if(deliveryTag % 2 == 0){ channel.basicAck(deliveryTag,false); }else{ // 第二个参数是否批量确认,第三个参数是否从新回队列 channel.basicNack(deliveryTag,false,true); } }
5. 如何保证消息不重复消费(如何保证消息的幂等性)
消息重复的缘由有两个:
-
生产时消息重复
因为生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,实际上MQ已经接收到了消息。这时候生产者就会从新发送一遍这条消息。
-
消费时消息重复。
消费者消费成功后,在给MQ确认的时候出现了网络波动,MQ没有接收到确认,为了保证消息被消费,MQ就会继续给消费者投递以前的消息。这时候消费者就接收到了两条同样的消息。
因为消息重复是网络波动等缘由形成的,没法避免,咱们能作的的就是保证消息的幂等性,以防业务重复处理。具体处理方案为:
让每一个消息携带一个全局的惟一ID,便可保证消息的幂等性,具体消费过程为:
- 消费者获取到消息后先根据id去查询redis/db是否存在该消息。
- 若是不存在,则正常消费,消费完毕后写入redis/db。
- 若是存在,则证实消息被消费过,直接丢弃。
@RabbitListener(queues = MqConfig.QUEUE) public void receive(Message message, Channel channel){ String messageId = message.getMessageProperties().getMessageId(); String body = new String(message.getBody()); String redisId = redisTemplate.opsForValue().get(messageId)+""; // 若是redis中存有当前消息的消息id // 则证实消费过 if(messageId.equals(redisId)){ return; } redisTemplate.opsForValue().set(messageId, UUID.randomUUID()); }
6. 消息大量堆积应该怎么处理
消息堆积的缘由有两个
- 网络故障,消费者没法正常消费
- 消费方消费后未进行ack确认
解决方案以下:
- 检查并修复消费者故障,使其正常消费
- 编写临时程序将堆积的消息发送到容量更大的MQ集群,增长消费者快速消费
- 堆积消息消费完毕后,中止临时程序,恢复正常消费
7. 死信是什么?死信如何处理
当一条消息在队列中出现如下三种状况的时候,该消息就会变成一条死信。
- 消息被拒绝(basic.reject / basic.nack),而且requeue = false
- 消息TTL过时
- 队列达到最大长度
当消息在一个队列中变成一个死信以后,若是配置了死信队列,它将被从新publish到死信交换机,死信交换机将死信投递到一个队列上,这个队列就是死信队列。
一条消息成为死信后,通常会经过死信队列进行存库,而后定时将库中的死信进行从新投递到消息队列上。
8. 若是我有一笔订单,30分钟未支付则关闭订单,使用RabbitMQ如何来实现
RabbitMQ可使用死信队列来实现延时消费,用户下单以后,将订单信息投递到消息队列中,而且设置消息过时时常为30分钟。若是用户支付则正常关闭订单,若是用户未支付,消息达到过时时间,消息会进入死信交换,由消费者进行消费死信队列来关闭订单。
9. RabbitMQ如何保证高可用
RabbitMQ有两种集群模式,分别是普通集群和镜像集群,普通模式没法保证RabbitMQ的高可用。
普通集群
假若有三个节点,rabbitmq一、rabbitmq二、rabbitmq3,消息实际上只存在于其中一个节点,三个节点仅有相同的元数据,即队列的结构,当消息进入rabbitmq2节点的queue后,consumer从rabbitmq1的节点进行消费,rabbitmq1和rabbitmq2会进行临时通讯,从rabbitmq2中获取消息而后返回给consumer。
这种模式存在如下两个问题:
-
当rabbitmq2宕机后,消息没法正常消费,没有作到真正的高可用
-
实际数据仍是在单个实例上,存在瓶颈问题
镜像集群
假若有三个节点,rabbitmq一、rabbitmq二、rabbitmq3,每一个实例之间均可以相互通讯,每次生产者写消息到queue的时候,每一个rabbitmq节点上都有queue的消息数据和元数据。这种模式使用于可靠性要求较高的场景。
点关注、不迷路
若是以为文章不错,欢迎关注、点赞、收藏,大家的支持是我创做的动力,感谢你们。
若是文章写的有问题,请不要吝惜文笔,欢迎留言指出,我会及时核查修改。
若是你还想看到更多别的东西,能够微信搜索「Java旅途」进行关注。回复“手册”领取Java面试手册!