RabbitMQメッセージ確認メカニズム(ACK)

1.メッセージ確認メカニズム(ACK)

メッセージがキューから確実にコンシューマーに到達することを保証するために、RabbitMQはメッセージ確認メカニズム(メッセージ確認)を提供します。コンシューマーは、キューにサブスクライブするときにautoAckパラメーターを指定できます。autoAckパラメーターがfalseに等しい場合、RabbitMQは、メモリー(またはディスク)からメッセージを削除する前に、コンシューマーが確認シグナルに明示的に応答するのを待ちます(実際には、最初に削除済みとしてマークされます。、後で削除されます)。autoAckパラメーターがtrueに等しい場合、RabbitMQは送信されたメッセージを確認応答として自動的に設定し、コンシューマーが実際にこれらのメッセージを消費するかどうかに関係なく、メモリ(またはディスク)からメッセージを削除します。

メッセージ確認メカニズムが採用された後、autoAckパラメーターがfalseに設定されている限り、コンシューマーはメッセージ(タスク)を処理するのに十分な時間があり、コンシューマープロセスがハングした後のメッセージ損失について心配する必要はありません。 RabbitMQは常にメッセージを待機するため、メッセージを処理するプロセス。メッセージは、コンシューマーが明示的にBasic.Ackコマンドを呼び出すまでありません。

autoAckパラメーターがfalseの場合、RabbitMQサーバー側の場合、キュー内のメッセージは2つの部分に分割されます。1つはコンシューマーへの配信を待機しているメッセージで、もう1つはコンシューマーに配信されたメッセージです。消費者確認信号はまだ受信されていませんニュース。RabbitMQサーバーがコンシューマーの確認シグナルを受信して​​おらず、このメッセージを消費するコンシューマーが切断されている場合、サーバーはメッセージがキューに再び入り、次のコンシューマー(または元のそのコンシューマー)への配信を待つように手配します。 )。

RabbitMQは、未確認のメッセージの有効期限を設定しません。メッセージをコンシューマーに再配信する必要があるかどうかを判断する唯一の根拠は、メッセージを消費する接続が切断されているかどうかです。この設定の理由は、時間です。 RabbitMQを使用すると、コンシューマーはメッセージを消費できます。これには長い時間がかかる場合があります。

RabbitMQ Web管理プラットフォームでは、コンシューマーへの配信を待機しているメッセージの数と配信されたメッセージに対応する、現在のキューの「準備完了」状態と「未確認」状態のメッセージの数を確認できます。消費者に、しかし確認信号、それぞれ番号を受信して​​いません。以下に示すように:

RabbitMQメッセージ確認メカニズムは、送信者確認と受信者確認の2つのカテゴリに分けられます。

送信者の確認は、プロデューサーから交換への確認、および交換からキューの確認に分けられます。以下に示すように:

 

2.メッセージ送信確認

2.1ConfirmCallbackメソッド

ConfirmCallbackはコールバックインターフェイスです。メッセージがブローカーに送信された後、メッセージがブローカーサーバーに到達したかどうかを確認するためにコールバックがトリガーされます。つまり、メッセージがExchangeに正しく到着したかどうかのみを確認します。

パブリッシャーの確認がオンになっていることを示すために、プロデューサーの構成に次の構成を追加する必要があります。

spring.rabbitmq.publisher-confirm-type=correlated # 新版本
spring.rabbitmq.publisher-confirms=true # 老版本

2.2ReturnCallbackメソッド

ReturnCallbackインターフェースを実装することにより、メッセージが失敗したときに開始メッセージが返されます。このインターフェースは、交換がキューをルーティングできないときにコールバックをトリガーします。交換とキューがコードにバインドされているため、このメソッドは使用できません。メッセージの場合コードが間違っていない限り、キューのバインドに失敗することはほとんどありません。

このインターフェースを使用するには、プロデューサー構成に構成を追加して、パブリッシャーが戻ることを示す必要があります。

spring.rabbitmq.publisher-returns=true

【例】送信者は、メッセージ送信確認機能(交換者確認、キュー確認)を実現します。

(1)最初のSpringBootプロジェクト(rabbitmq-プロバイダーメッセージングプロジェクト)を作成します。

pom.xml構成情報ファイルで、関連する依存ファイルを追加します。

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.4.1</version>
</dependency>

application.yml構成ファイルでRabbitMQサービスを構成します。

spring:
  # 项目名称
  application:
    name: rabbitmq-provider
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    # 消息确认(ACK)
    publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)

(2)キューを構成します

rabbitmq-provider(メッセージ送信プロジェクト)で、メッセージ確認、キュー名などを構成し、キューをIoC管理に送信すると、コードは次のようになります。

package com.pjb.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    public static final String QUEUE_NAME = "queue_name"; //队列名称
    public static final String EXCHANGE_NAME = "exchange_name"; //交换器名称
    public static final String ROUTING_KEY = "routing_key"; //路由键

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory)
    {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);

        //确认消息送到交换机(Exchange)回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback()
        {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause)
            {
                System.out.println("\n确认消息送到交换机(Exchange)结果:");
                System.out.println("相关数据:" + correlationData);
                System.out.println("是否成功:" + ack);
                System.out.println("错误原因:" + cause);
            }
        });

        //确认消息送到队列(Queue)回调
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback()
        {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage)
            {
                System.out.println("\n确认消息送到队列(Queue)结果:");
                System.out.println("发生消息:" + returnedMessage.getMessage());
                System.out.println("回应码:" + returnedMessage.getReplyCode());
                System.out.println("回应信息:" + returnedMessage.getReplyText());
                System.out.println("交换机:" + returnedMessage.getExchange());
                System.out.println("路由键:" + returnedMessage.getRoutingKey());
            }
        });
        return rabbitTemplate;
    }

    /**
     * 队列
     */
    @Bean
    public Queue queue()
    {
        /**
         * 创建队列,参数说明:
         * String name:队列名称。
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化的队列会存盘,在服务器重启的时候不会丢失相关信息。
         * boolean exclusive:设置是否排他,默认也是 false。为 true 则设置队列为排他。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         * 当没有生产者或者消费者使用此队列,该队列会自动删除。
         * Map<String, Object> arguments:设置队列的其他一些参数。
         */
        return new Queue(QUEUE_NAME, true, false, false, null);
    }

    /**
     * Direct交换器
     */
    @Bean
    public DirectExchange exchange()
    {
        /**
         * 创建交换器,参数说明:
         * String name:交换器名称
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         */
        return new DirectExchange(EXCHANGE_NAME, true, false);
    }

    /**
     * 绑定
     */
    @Bean
    Binding binding(DirectExchange exchange, Queue queue)
    {
        //将队列和交换机绑定, 并设置用于匹配键:routingKey
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }
}

(3)送信者を作成する

rabbitmq-provider(メッセージ送信プロジェクト)で、送信者を作成し、rabbitTemplate.convertAndSend()メソッドを使用してメッセージを送信します。

同時に、routingKeyパラメーターはコードのエラーに意図的に書き込まれるため、キュー障害コールバックに確認メッセージを送信する必要があります。コードは次のとおりです。

package com.pjb;

import com.pjb.config.RabbitMqConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * RabbitMq测试类
 * @author pan_junbiao
 **/
@SpringBootTest
public class RabbitMqTest
{
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void sendMessage() throws Exception
    {
        String message = "您好,欢迎访问 pan_junbiao的博客";

        //这里故意将routingKey参数写入错误,让其应发确认消息送到队列失败回调
        rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, "no_queue_name", message);

        //由于这里使用的是测试方法,当测试方法结束,RabbitMQ相关的资源也就关闭了,
        //会导致消息确认的回调出现问题,所有加段延时
        Thread.sleep(2000);
    }
}

結果:

 

3.メッセージ受信の確認

コンシューマーは、リスニングキューで発生するコンシューマー処理ビジネスの障害(例外の発生、要件を満たしていないデータなど)を確認します。再送信や破棄など、これらのシナリオを手動で処理する必要があります。

RabbitMQメッセージ確認メカニズム(ACK)は、デフォルトで自動的に確認されます。自動確認は、メッセージがコンシューマーに送信された直後に確認されますが、メッセージが失われる可能性があります。コンシューマーロジックが例外をスローした場合、ロールバックを使用します。データの整合性は保証されますが、メッセージは失われます。つまり、コンシューマーはメッセージを正常に処理できず、メッセージを失うことと同じです。

メッセージ確認モードは次のとおりです。

  1. AcknowledgeMode.NONE:自動確認。
  2. AcknowledgeMode.AUTO:状況に応じて確認します。
  3. AcknowledgeMode.MANUAL:手動確認。

メッセージを受信した後、コンシューマーは手動でBasic.AckまたはBasic.NackまたはBasic.Rejectを呼び出し、RabbitMQはこれらのメッセージを受信した後に配信が完了したと見なします。

  1. Basic.Ackコマンド:現在のメッセージを確認するために使用されます。
  2. Basic.Nackコマンド:現在のメッセージを無効にするために使用されます(注:これはAMQP 0-9-1のRabbitMQ拡張です)。
  3. Basic.Rejectコマンド:現在のメッセージを拒否するために使用されます。

3.1basicAckメソッド

basicAckメソッドは、現在のメッセージを確認するために使用されます。ChannelクラスのbasicAckメソッドは、次のように定義されています。

void basicAck(long deliveryTag, boolean multiple) throws IOException;

パラメータの説明:

長いdeliveryTag:一意の識別ID。コンシューマーがRabbitMQに登録すると、チャネルが確立されます。RabbitMQは、basic.deliverメソッドを使用してメッセージをコンシューマーにプッシュします。このメソッドは、RabbitMQをチャネルに表す配信タグを伝送します。配信されたメッセージの一意の識別IDは単調に増加する正の整数であり、配信タグの範囲はチャネルに制限されます。

ブール倍数:バッチ処理かどうかにかかわらず、このパラメーターがtrueの場合、delivery_tagが着信値以下のすべてのメッセージを一度に確認できます。

3.2 basicNack 方法

basicNackメソッドは、現在のメッセージを無効にするために使用されます。basicRejectメソッドは一度に1つのメッセージしか拒否できないため、メッセージをバッチで拒否する場合は、basicNackメソッドを使用できます。コンシューマークライアントはchannel.basicNackメソッドを使用して達成できます。このメソッドは、次のように定義されています。

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

パラメータの説明:

long deliveryTag:一意の識別ID。

ブール倍数:上記で説明しました。

boolean requeue: requeueパラメーターがtrueに設定されている場合、RabbitMQはメッセージをキューに再保存して、次のサブスクライブされたコンシューマーに送信できるようにします。requeueパラメーターがfalseに設定されている場合、RabbitMQはメッセージをキューさらに、新しいコンシューマーには送信されません。

3.3basicRejectメソッド

basicRejectメソッドは、現在のメッセージを確認するのではなく、明示的に拒否するために使用されます。RabbitMQは、バージョン2.0.0でBasic.Rejectコマンドを導入しました。コンシューマークライアントは、対応するchannel.basicRejectメソッドを呼び出して、このメッセージを拒否するようにRabbitMQに指示できます。

ChannelクラスのbasicRejectメソッドは次のように定義されています。

void basicReject(long deliveryTag, boolean requeue) throws IOException;

パラメータの説明:

long deliveryTag:一意の識別ID。

boolean requeue:これは上で説明されています。

【例】コンシューマークライアントがメッセージ受信確認を実現します。

(1)2番目のSpringBootプロジェクト(rabbitmq-コンシューマーメッセージ受信プロジェクト)を作成します。

pom.xml構成情報ファイルで、関連する依存ファイルを追加します。

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.4.1</version>
</dependency>

application.yml構成ファイルでRabbitMQサービスを構成します。

spring:
  # 项目名称
  application:
    name: rabbitmq-consumer
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

(2)構成情報

rabbitmq-consumer(メッセージ受信プロジェクト)で、メッセージの手動確認とメッセージ受信の確認を設定します。

package com.pjb.config;

import com.pjb.receiver.Receiver;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    @Autowired
    private CachingConnectionFactory connectionFactory;

    @Autowired
    private Receiver receiver; //消息接收处理类

    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer()
    {
        //消费者数量,默认10
        int DEFAULT_CONCURRENT = 10;
    
        //每个消费者获取最大投递数量 默认50
        int DEFAULT_PREFETCH_COUNT = 50;
    
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setConcurrentConsumers(DEFAULT_CONCURRENT);
        container.setMaxConcurrentConsumers(DEFAULT_PREFETCH_COUNT);
    
        // RabbitMQ默认是自动确认,这里改为手动确认消息
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    
        //设置一个队列
        container.setQueueNames("queue_name");
    
        //如果同时设置多个如下: 前提是队列都是必须已经创建存在的
        //container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");
        //另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
        //container.setQueues(new Queue("TestDirectQueue",true));
        //container.addQueues(new Queue("TestDirectQueue2",true));
        //container.addQueues(new Queue("TestDirectQueue3",true));
    
        container.setMessageListener(receiver);
    
        return container;
    }
}

(3)レシーバーを作成する

rabbitmq-consumer(メッセージ受信プロジェクト)で、レシーバーを作成します。

package com.pjb.receiver;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

/**
 * 接收者
 * @author pan_junbiao
 **/
@Component
public class Receiver implements ChannelAwareMessageListener
{
    @Override
    public void onMessage(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));

            /**
             * 确认消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             */
            channel.basicAck(deliveryTag, true);

            /**
             * 否定消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            e.printStackTrace();

            /**
             * 拒绝消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            channel.basicReject(deliveryTag, true);
        }
    }
}

結果:

 

【例】複数のキューを監視し、異なるメッセージ受信確認を行うように設定します。

(1)構成クラスを変更します

上記のRabbitMqConfig.java構成クラスで、複数のキューを追加します。

(2)受信者を変更する

package com.pjb.receiver;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

/**
 * 接收者
 * @author pan_junbiao
 **/
@Component
public class Receiver implements ChannelAwareMessageListener
{
    @Override
    public void onMessage(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            if ("queue_name".equals(message.getMessageProperties().getConsumerQueue()))
            {
                System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());
                System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));
                System.out.println("执行queue_name中的消息的业务处理流程......");
            }

            if ("fanout.A".equals(message.getMessageProperties().getConsumerQueue()))
            {
                System.out.println("消费的消息来自的队列名为:" + message.getMessageProperties().getConsumerQueue());
                System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));
                System.out.println("执行fanout.A中的消息的业务处理流程......");
            }

            /**
             * 确认消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             */
            channel.basicAck(deliveryTag, true);

            /**
             * 否定消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            e.printStackTrace();

            /**
             * 拒绝消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            channel.basicReject(deliveryTag, true);
        }
    }
}

結果:

 

おすすめ

転載: blog.csdn.net/pan_junbiao/article/details/112956537