RabbitMQ消息确认模式

在RabbitMQ中,默认情况下,生产者将消息发送到Broker,Broker是不会返回任何信息给生产者的,生产者并不知道消息是否正确到达Broker。如果消息在到达Broker之前,或者Broker将消息写入磁盘前发生了宕机,消息就丢失了,而生产者并不知道。
这个问题有两种解决机制,一种是AMQP协议层提供的事务机制;另一种是把Channel设置为Comfirm模式。

1、事务机制

在AMQP中把信道设置成事务模式后,生产者和Broker之间会有一种发送/响应机制判断当前命令操作是否可以继续。RabbitMQ中与事务模式相关的方法有三个:channel对象的txSelect(), txCommit()以及txRollback():

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

关键代码:

try {
    channel.txSelect(); // 声明事务
    // 发送消息
    channel.basicPublish("", _queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));
    channel.txCommit(); // 提交事务
} catch (Exception e) {
    channel.txRollback();
} finally {
    channel.close();
}

如果消息成功到达Broker则提交成功,否则抛出异常执行回滚。
由于事务模式需要生产者应用同步等待Broker的执行结果,在性能上会极大降低消息服务的吞吐量,所以不建议使用事务模式,而建议采用性能更好的发送确认模式来保证可靠性。

2、发送方确认模式

实现原理
发送方确认模式是RabbitMQ对AMQP的扩展实现。把信道设置成确认模式后,在该信道上发布的消息会被分配一个唯一的id,一旦消息被投递到对应的队列中,该信道就会给该消息的发送者发送确认消息,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,该确认消息包含消息的唯一id,从而让生产者知道消息已到达队列。
将channel设置成Confirm模式之后,后续所有通过该channel发送的消息都会被生产者异步等待确认,发送房对Broker发送confirm消息的快慢没有要求。

设置发送方确认模式的三种途径

普通确认:生产者每发送一条消息,就调用waitForConfirms()方法等待Broker的confirm消息,本质就是串行方式的确认。
关键代码:

channel.confirmSelect();//开启发送方确认模式
channel.basicPublish(exchangeName, routingKey, null, message.getBytes("UTF-8"));
if(!channel.waitForConfirms()){
    System.out.println("消息发送失败");
}

批量确认:生产者每发完一批消息,再调用channel.waitForConfirmsOrDie(),使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未被确认就会抛出IOException异常。
关键代码:

channel.confirmSelect();//开启发送方确认模式
for(int i=0;i<50;i++){
    channel.basicPublish(exchangeName, routingKey, null, message.getBytes("UTF-8"));
}
channel.waitForConfirmsOrDie(); //直到所有信息都发布,只要有一个未确认就会IOException
system.out.println("所有消息发送成功");

异步确认:通过调用addConfirmListener方法注册回调,在Broker确认接收到一条或多条消息之后由客户端回调该方法。
关键代码:

// 开启发送方确认模式
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
    String message = String.format("时间 => %s", new Date().getTime());
    channel.basicPublish("", queueName, null, message.getBytes("UTF-8"));
}
//异步监听确认和未确认的消息
channel.addConfirmListener(new ConfirmListener() {
    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("未确认消息,标识:" + deliveryTag);
    }
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        System.out.println(String.format("已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
    }
});

三种方式对比:普通确认模式编程复杂度最低,只需要考虑Broker返回false或者超过给定时间未返回,客户端进行重传即可。批量确认模式在发生异常情况下整个批次的消息都会重发,会造成消息重复,在消息丢失比较严重的场景下,这种模式不适用。异步模式最为复杂,每个信道都维护一个尚未确认的消息集合,每次发布消息时集合元素总数加1,执行回调时在减去确认收到的消息数量。异步方式只需要少量的生产者线程就可以达到良好的吞吐量。

3、消费者应答

有时候消息生产者不仅关心消息是否正确到达Broker,还想知道消息是否被消费者成功消费。RabbitMQ通过消费者回执(Consumer Acknowledgment)提供支持。
在实际应用中可能发生消费者接收到消息,但没处理完就宕机的情况,此时消息就丢失了。针对这种情况,消费者在消费完消息后给RabbitMQ服务器发送一个回执,服务器收到回执消息后再将消息从队列中删除;如果服务器没有收到回执消息并且对应消费者断开连接,则由服务器将该消息发送给其他消费者进行处理。RabbitMQ里的消息是不会过期的,如果消费者没有断开连接,服务器就不会将消息发给其他消费者。
两种消息回执的方式
在AMQP协议中定义了两种消息回执的方式,一种是自动回执,另一种是手动回执。
自动回执:在自动回执模式下Broker将消息发送给消费者后,会立即把此消息从队列中删除,不用等待消费者发送确认消息。
手动回执:在手动回执模式下,当Broker发送消息给消费者后并不会立即删除此消息,而是等待收到消费者发来的ACK指令后才会删除。
开启自动回执的方式
com.rabbitmq.clint.Channel接口中的basicConsume方法用来定义消息消费方法,其中一个重载方法第二个参数为boolean,代表是否开启自动回执。用例关键代码:

while (true){
            //消费消息
            boolean autoAck = false;//设置手动回执
            String consumerTag = "";
            channel.basicConsume(queueName,autoAck,consumerTag,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                                           AMQP.BasicProperties properties,byte[] body) throws IOException {
                    String message = new String(body,"UTF-8");
                    try{
						//dosomething(message)
						}finally{
							//对处理好的消息进行应答
							channel.basicAck(envelope.getDeliveryTag(),false);
						}
                }
            });
        }
发布了43 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_41172473/article/details/85278182