头图

如何保证使用rabbitmq时,消息不丢失?

其实这个问题适用于任何mq,虽然不同的mq具体操作上有区别,但大体上需要从三个方面考虑:生产者发送、broker存储和消费者接收均保证消息可靠

image.png

我们来看看rabbitmq是怎么做到这三点的。

一、生产者消息可靠

生产者保证消息可靠有两种方式,事务confirm机制

1.事务控制

rabbitmq允许通过事务的方式发送消息。

关键代码如下:

// == 将信道设置成事务模式
channel.txSelect
try {
    // 这里发送消息
} catch (Exception e) {
    // == 事务回滚
    channel.txRollback
}

// == 提交事务
channel.txCommit

如果出现问题可以回滚事务,之后做重发之类的操作。

问题在于事务是同步的,后续的操作都会等待事务执行而阻塞在这里,影响性能。

2.confirm机制

  • 单条confirm

    // == 信道开启消息确认
    channel.confirmSelect();   
    // 单条发送     
    channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
    if (channel.waitForConfirms()){
      // 消息发送成功
    }else {
      // 消息发送失败
    }

    每条数据通过channel.waitForConfirms()获取发送结果

  • 批量confirm

    // == 信道开启消息确认
    channel.confirmSelect();
    // 批量发送
    for (int i = 0; i < 10; i++) {
      channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    }
    //直到所有信息都发布,只要有一个未确认就会IOException
    channel.waitForConfirmsOrDie(); 

    一个批次通过channel.waitForConfirmsOrDie()获取发送结果。

批次发送失败,需要整个批次重发;消费端需要做好重复消息的处理,一般在业务层面做好幂等

  • 异步confirm
    前两种都是同步的确认,异步确认能将效率能最大化。

    channel.addConfirmListener(new ConfirmListener() {
      // == 消息失败处理
      @Override
      public void handleNack(long deliveryTag, boolean multiple) throws IOException {
          //deliveryTag;唯一消息标签
          //multiple:是否批量
          System.err.println("-------no ack!-----------");
      }
      
      // == 消息成功处理
      @Override
      public void handleAck(long deliveryTag, boolean multiple) throws IOException {
          System.err.println("-------ack!-----------");
      }
    });

    无论成功还是失败,都会回调listener中的一个方法。

二、broker消息可靠

持久化

首先要开启持久化,分为队列持久化和消息持久化。

  • 队列持久化:
    在创建队列时将channel.queueDeclare()第二个参数改为true。
  • 消息持久化:
    在使用信道发送消息时channel.basicPublish()将第三个参数改为:MessageProperties.PERSISTENT_TEXT_PLAIN表示持久化消息。

同时开启以上两种持久化机制,这样消息到达broker时,rabbitmq会做落盘操作。

rabbitmq的消息落盘也有很小的时间间隔,有可能刚写入系统缓存服务器就挂了
因此还要开启rabbitmq的镜像集群,在写入主的同时也将数据同步到从服务。

三、消费者消息可靠

消息确认模式有:

  • AcknowledgeMode.NONE:自动确认(默认)。
  • AcknowledgeMode.AUTO:根据情况确认。
  • AcknowledgeMode.MANUAL:手动确认。

手动ack

消费者成功接收消息时,默认会自动向broker发送ack,表示消息接收。
我们需要改为手动ack模式。

一张图总结

image.png

经过上述讨论,消息可靠性需要牺牲一定的性能,从而降低mq的吞吐量,因此需要在可靠性和吞吐量之间寻求一个平衡。

考虑一下:当前业务场景是否对消息的可靠性有这么高的要求,同时实时性也要保证呢?如果实时性要求不高,能否通过生产、消费两端核对的方式,达到可靠性要求呢?

附录

P6-P7知识合辑


青鱼
268 声望25 粉丝

山就在那里,每走一步就近一些