RabbitMQ常见问题

1、如何确保消息正确地发送至RabbitMQ?

  两种解决方法:

  • 通过事务机制
    • 事务正常:
      • 客户端发送Tx.Select,将信道置为事务模式
      • Broker回复Tx.Select-Ok,确认已将信道设置为事务模式
      • 在发送完消息后,客户端发送Tx.Commit提交事务
      • Broker回复Tx.Commit-Ok确认事务提交 
    • 回滚:
      • 客户端发送Tx.Select,将信道置为事务模式
      • Broker回复Tx.Select-Ok,确认已将信道设置为事务模式
      • 事务提交之前出现异常,我们可以将其捕获,进而通过执行channel.exRollback方法实现事务回滚
  • 发送方确认
    • 信道设置为confirm模式,所有在该信道发送的消息都会被指派一个唯一ID
    • 一旦消息被投递到所匹配的队列中,给生产者发送一个Basic.ack消息(包含ID),这样使得生产者知道消息已到达目的地,如果消息和队列是持久化的,那么确认消息会在消息写入磁盘后发出。
    • 如果Rabbit因为自身内部错误导致消息丢失,就会发送一条Basic.Nack命令,生产者应用可以在回调函数中处理该Nack命令     
    public void syncSend(Integer id){
        ProducerSyncConfirmMessage message = new ProducerSyncConfirmMessage();
        message.setId(id);
        rabbitTemplate.invoke(new RabbitOperations.OperationsCallback<Object>() {
            
            @Override
            public Object doInRabbit(RabbitOperations operations) {
                operations.convertAndSend(ProducerSyncConfirmMessage.EXCHANGE, ProducerSyncConfirmMessage.ROUTING_KEY, message);
                logger.info("[doInRabbit][发送消息完成]");
                operations.waitForConfirms(0); // timeout 参数,如果传递 0 ,表示无限等待
                logger.info("[doInRabbit][等待 Confirm 完成]");
                return null;
            }
        }, new ConfirmCallback() {
            @Override
            public void handle(long deliveryTag, boolean multiple) throws IOException {
                logger.info("[handle][Confirm 成功]");
            }
        }, new ConfirmCallback() {
            @Override
            public void handle(long deliveryTag, boolean multiple) throws IOException {
                logger.info("[handle][Confirm 失败]");

            }
        });
    }

 

2、如何确保消息接收方消费了消息?

  ack机制,消费者在订阅队列时,可以指定autoAck参数:

  • autoAck == false :会等待消费者显示的回复信息后才从内存中删除(先标记,收到确认命令后删除),如果一直没有收到确认命令,并且此消费者已经断开连接,那么RabbitMQ会安排该消息重新进入队列。
  • autoAck == true : RabbitMQ会自动把发送出去的消息从内存中删除,不管消费者有没有消费

3、如何避免消息重复消费?

  重复消费:保证消息唯一识别性,即使多次发送到消费者,消费者也可以检验是否已消费。

4、消息如何分发?

  • 当RabbitMQ队列拥有多个消息者时,队列收到的信息将以轮询的分发方式发给消费者。每条消息都只会发送给订阅列表中的一个消费者。
  • 默认情况下,如果有n个消费者,那么RabbitMQ会将第m条消息分发给第m%n(取余)个消费者,RabbitMQ不管消费者是否消费并经确认(Basic.Ack)了消息

  如果某些消费者业务繁重,来不及消费这么多消息,而某些消费者由于某些原因很快结束了,进而进程空闲那么造成整体吞吐量下降。可以使用channel.basicQos(int prefetchCount)方法,该方法允许限制信道上的消费者所能保持的最大未确认消息的数量。举例说明:如果订阅消费列表之前,消费端调用了channel.basicQos(5),之后订阅了某个队列进行消费。RabbitMQ会先保存一个消费者的队列,每发送一条消息都会为对应的消费者计数,如果达到所设定的上线。RabbitMQ不会向这个消费者发送消息,待消费者确认消费某条消息之后,计数-1,之后消费者就可以继续接收消息了。

5、消息怎么路由?

  生产者发送消息到交换器,交换器根据绑定(RoutingKey 和 BindingKey根据交换器的类型)匹配对应的Queue 

  Exchange由四个类型:fanout、direct、topic、headers

6、如何确保消息不丢失?

  • 生产者发送消息丢失:可以采取生产者confirm模式 ,autoAck == false,如果没有收到Basic.Ack命令需要进行消息补偿:
    • 第一种:消息发送前落库并进行状态标记,待收到确认信号再更改状态。定时任务检查超时没有收到确认的消息,进行重发但是要设置重发次数。达到一定次数进行异常处理人工介入
    • 第二种:
      • 生产者生产两条消息,一条立即发送给下游服务,一条延迟消息给补偿(callback Server)服务
      • 下游服务监听到这个消息后,会发送一条消息到callback Server
      • callback Server监听到这个消息后,知道刚才有条消息消费成功了,然后把这个持久化到数据库中,当上游服务发送的延迟消息到达callback Server时,就会查询db是否存在对应的消息。如果存在就代表已经被消费了。如果不存在这条记录,那么callback Server就会发送一个RPC请求到上游进行消息重发。
  • rabbitmq消息丢失:将队列和消息都持久化(队列:durable == true 、消息:deliveryMode == 2),但是RabbitMQ不会为每条消息进行磁盘同步,可能先写入缓存(三个策略:),待缓存满了之后再进行写磁盘操作。那么这段时间服务器宕机也会带来消息的问题,解决的方案就是镜像队列,如果master节点宕机,可以自动切换到从节点,这样有效保证了高可用性。
  • 消费者丢失消息:消费者confirm模式,autoAck == false

7、保证消息队列的高可用?

  开启镜像集群模式,你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。

8、保证消息的顺序性?

  • 拆分多个queue,每个queue一个consumer,这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。

  • 或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

posted @ 2020-08-10 16:49  TPL  阅读(243)  评论(0编辑  收藏  举报