RabbitMQ面试考点 可靠消息&延迟队列

文章目录

    • 一、如何保证消息可靠性
      • 1、生产者确认
      • 2、事务机制
      • 3、持久化
      • 4、消费者确认机制
      • 5、失败重试机制
    • 二、死信交换机
      • 1、什么样的消息会成为死信?
      • 2、如何给队列绑定死信交换机?
      • 3、如何实现发送一个消息20秒后消费者才收到消息?
    • 三、延迟队列
    • 四、惰性队列
      • 消息堆积问题
      • 惰性队列
    • 五、MQ集群
      • 两种模式
      • 镜像模式

一、如何保证消息可靠性

  • 生产者确认机制confirmCallback(通过confirm回调的result判断结果)和returnCallBack(消息到达队列失败触发的回调)
  • 生产者事务
  • 消息持久化
  • 消费者确认
  • 失败重试

1、生产者确认

confirmCallback和returnCallback

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的id(从1开始),一旦消息被投递到匹配的队列之后,rabbitmq就会发送一个确认(Basic.Ack)和deliverTag(消息id)给生产者。

如果消息和队列是持久化的那么消息会在持久化之后被发出。rabbitmq除了回传deliverTag之外,还有multiple参数,表示到这个序号之前所有的消息都得到了处理。所有的消息只会被Ack或Nack一次,不会出现既被Ack又被Nack

消息没没有到达交换机,通过confirmCallback的result获取,Nack表现没有正常到达队列

		// 1.准备消息
        String message = "hello, spring amqp!";
        // 2.准备CorrelationData
        // 2.1.消息ID
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 2.2.准备ConfirmCallback
        correlationData.getFuture().addCallback(result -> {
            // 判断结果
            if (result.isAck()) {
                // ACK
                log.debug("消息成功投递到交换机!消息ID: {}", correlationData.getId());
            } else {
                // NACK
                log.error("消息投递到交换机失败!消息ID:{}", correlationData.getId());
                // 重发消息
            }
        }, ex -> {
            // 记录日志
            log.error("消息发送失败!", ex);
            // 重发消息
        });
        // 3.发送消息
        rabbitTemplate.convertAndSend("amq.topic", "a.simple.test", message, correlationData);

消息到达交换机但没到达队列,触发returnCallback

        // 配置ReturnCallback,消息到达队列失败触发
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 判断是否是延迟消息
            Integer receivedDelay = message.getMessageProperties().getReceivedDelay();
            if (receivedDelay != null && receivedDelay > 0) {
                // 是一个延迟消息,忽略这个错误提示
                return;
            }
            // 记录日志
            log.error("消息发送到队列失败,响应码:{}, 失败原因:{}, 交换机: {}, 路由key:{}, 消息: {}",
                     replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有需要的话,重发消息
        });

2、事务机制

rabbitmq与事务相关的方法:

  • channel.txSelect(): 将当前信道设置成事务模式
  • channel.txCommit(): 用于提交事务
  • channel.txRollback(): 用于回滚事务

通过事务实现机制,只有消息成功被rabbitmq服务器接收,事务才能提交成功,否则便可在捕获异常之后进行回滚,然后进行消息重发,但是事务非常影响rabbitmq的性能。还有就是事务机制是阻塞的过程,只有等待服务器回应之后才会处理下一条消息

示例:

public class TransactionSender {


    private static final String ex_name = "ex_tx";
    private static final String q_name = "q_tx";
    private static final String rt_name = "rt_tx";

    public static void main(String[] args) throws IOException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(ex_name, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(q_name, false, false, false, null);
        channel.queueBind(q_name, ex_name, rt_name);

        channel.txSelect();
        try {
            channel.basicPublish(ex_name, rt_name, null, "hello".getBytes());
            //显示抛出RuntimeException
            int a = 1/0;
            channel.txCommit();
        } catch (IOException e) {
            e.printStackTrace();
            channel.txRollback();
        }
    }
}

3、持久化

交换机持久化、队列持久化、消息持久化

4、消费者确认机制

消费者设置手动提交或自动提交

RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。而SpringAMQP则允许配置三种确认模式:

  • manual:手动ack,需要在业务代码结束后,调用api发送ack。

  • auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack

  • none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除

5、失败重试机制

当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue,无限循环,导致mq的消息处理飙升,带来不必要的压力

MessageRecoverer,重试耗尽后,将失败消息投递到指定的交换机,由人工进行处理

@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
	return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}

二、死信交换机

1、什么样的消息会成为死信?

  • 消息被消费者reject或者返回nack

  • 消息超时未消费

  • 队列满了

2、如何给队列绑定死信交换机?

  • 给队列设置dead-letter-exchange属性,指定一个交换机

  • 给队列设置dead-letter-routing-key属性,设置死信交换机与死信队列的RoutingKey

3、如何实现发送一个消息20秒后消费者才收到消息?

  • 给消息的目标队列指定死信交换机

  • 消费者监听与死信交换机绑定的队列

  • 发送消息时给消息设置ttl为20秒

三、延迟队列

利用TTL结合死信交换机,我们实现了消息发出后,消费者延迟收到消息的效果。这种消息模式就称为延迟队列(Delay Queue)模式。

因为延迟队列的需求非常多,所以RabbitMQ的官方也推出了一个插件,原生支持延迟队列效果。

延迟队列插件的使用步骤包括哪些?

  • 声明一个交换机,添加delayed属性为true

  • 发送消息时,添加x-delay头,值为超时时间

四、惰性队列

消息堆积问题

  • 增加更多消费者,提高消费速度

  • 在消费者内开启线程池加快消息处理速度

  • 扩大队列容积,提高堆积上限

惰性队列

指定x-queue-mode属性为lazy即可

  • 接收到消息后直接存入磁盘而非内存

  • 消费者要消费消息时才会从磁盘中读取并加载到内存

  • 支持数百万条的消息存储

优点:

  • 基于磁盘存储,消息上限高

  • 没有间歇性的page-out,性能比较稳定

缺点:

  • 基于磁盘存储,消息时效性会降低

  • 性能受限于磁盘的IO

五、MQ集群

两种模式

  • 普通模式:普通模式集群不进行数据同步,只会进行元数据信息(交换器、队列、绑定关系、vhost元数据)的同步。例如我们有2个MQ:mq1,和mq2,如果你的消息在mq1,而你连接到了mq2,那么mq2会去mq1拉取消息,然后返回给你。如果mq1宕机,消息就会丢失。
  • 镜像模式:与普通模式不同,队列会在各个mq的镜像节点之间同步,因此你连接到任何一个镜像节点,均可获取到消息。而且如果一个节点宕机,并不会导致数据丢失。不过,这种方式增加了数据同步的带宽消耗。

获取cookie

RabbitMQ底层依赖于Erlang,而Erlang虚拟机就是一个面向分布式的语言,默认就支持集群模式。集群模式中的每个RabbitMQ 节点使用 cookie 来确定它们是否被允许相互通信。

要使两个节点能够通信,它们必须具有相同的共享秘密,称为Erlang cookie。cookie 只是一串最多 255 个字符的字母数字字符。

每个集群节点必须具有相同的 cookie。实例之间也需要它来相互通信。

镜像模式

  • 镜像队列结构是一主多从(从就是镜像)
  • 所有操作都是主节点完成,然后同步给镜像节点
  • 主宕机后,镜像节点会替代成新的主(如果在主从同步完成前,主就已经宕机,可能出现数据丢失)
  • 不具备负载均衡功能,因为所有操作都会有主节点完成(但是不同队列,其主节点可以不同,可以利用这个提高吞吐量)

镜像队列是基于普通的集群模式的

2.1. 镜像队列的结构
镜像队列基本上就是一个特殊的BackingQueue,它内部包裹了一个普通的BackingQueue做本地消息持久化处理,在此基础上增加了将消息和ack复制到所有镜像的功能。所有对mirror_queue_master的操作,会通过可靠组播GM的方式同步到各slave节点。GM负责消息的广播,mirror_queue_slave负责回调处理,而master上的回调处理是由coordinator负责完成。mirror_queue_slave中包含了普通的BackingQueue进行消息的存储,master节点中BackingQueue包含在mirror_queue_master中由AMQQueue进行调用。

消息的发布(除了Basic.Publish之外)与消费都是通过master节点完成。master节点对消息进行处理的同时将消息的处理动作通过GM广播给所有的slave节点,slave节点的GM收到消息后,通过回调交由mirror_queue_slave进行实际的处理。

对于Basic.Publish,消息同时发送到master和所有slave上,如果此时master宕掉了,消息还发送slave上,这样当slave提升为master的时候消息也不会丢失。

2.2. GM(Guarenteed Multicast)
GM模块实现的一种可靠的组播通讯协议,该协议能够保证组播消息的原子性,即保证组中活着的节点要么都收到消息要么都收不到。

它的实现大致如下:

将所有的节点形成一个循环链表,每个节点都会监控位于自己左右两边的节点,当有节点新增时,相邻的节点保证当前广播的消息会复制到新的节点上;当有节点失效时,相邻的节点会接管保证本次广播的消息会复制到所有的节点。在master节点和slave节点上的这些gm形成一个group,group(gm_group)的信息会记录在mnesia中。不同的镜像队列形成不同的group。消息从master节点对于的gm发出后,顺着链表依次传送到所有的节点,由于所有节点组成一个循环链表,master节点对应的gm最终会收到自己发送的消息,这个时候master节点就知道消息已经复制到所有的slave节点了。

你可能感兴趣的:(中间件,rabbitmq,消息队列)