RabbitMQはどのようにしてメッセージの信頼できる配信を保証しますか?

SpringBootはRabbitMQを統合します

Springには3つの構成方法があります

  1. XMLに基づく
  2. JavaConfigに基づく
  3. 注釈ベース

もちろん、現在XMLが構成に使用されることはめったにありません。JavaConfigとアノテーションを使用した構成方法を紹介するだけです。

RabbitMQはSpringBootを統合しているため、対応するスターターを追加するだけで済みます。

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>

注釈ベース

application.yamlの設定は次のとおりです

spring:
  rabbitmq:
    host: myhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /

log:
  exchange: log.exchange
  info:
    queue: info.log.queue
    binding-key: info.log.key
  error:
    queue: error.log.queue
    binding-key: error.log.key
  all:
    queue: all.log.queue
    binding-key: '*.log.key'

コンシューマーコードは次のとおりです

@Slf4j
@Component
public class LogReceiverListener {

    /**
     * 接收info级别的日志
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = "${log.info.queue}", durable = "true"),
                    exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
                    key = "${log.info.binding-key}"
            )
    )
    public void infoLog(Message message) {
        String msg = new String(message.getBody());
        log.info("infoLogQueue 收到的消息为: {}", msg);
    }

    /**
     * 接收所有的日志
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = "${log.all.queue}", durable = "true"),
                    exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
                    key = "${log.all.binding-key}"
            )
    )
    public void allLog(Message message) {
        String msg = new String(message.getBody());
        log.info("allLogQueue 收到的消息为: {}", msg);
    }
}

生産者は以下の通りです

@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgProducerTest {

    @Autowired
    private AmqpTemplate amqpTemplate;
    @Value("${log.exchange}")
    private String exchange;
    @Value("${log.info.binding-key}")
    private String routingKey;

    @SneakyThrows
    @Test
    public void sendMsg() {
        for (int i = 0; i < 5; i++) {
            String message = "this is info message " + i;
            amqpTemplate.convertAndSend(exchange, routingKey, message);
        }

        System.in.read();
    }
}

メッセージackに対するSpringBootのアプローチは、ネイティブAPIがメッセージackをターゲットにする方法とは少し異なります。

ネイティブAPIメッセージのACKメソッド

メッセージを確認する方法は2つあります

自動確認(autoAck = true)
手動確認(autoAck = false)

コンシューマーがメッセージを消費するとき、autoAckパラメーターを指定できます

String basicConsume(String queue、boolean autoAck、Consumer callback)

autoAck = false:RabbitMQは、メモリ(またはディスク)からメッセージを削除する前に、コンシューマーが応答確認メッセージを表示するのを待ちます

autoAck = true:RabbitMQは、送信されたメッセージを確認として自動的に設定し、コンシューマーが実際にこれらのメッセージを消費するかどうかに関係なく、メモリ(またはディスク)からメッセージを削除します。

手動確認の方法は以下の通りで、2つのパラメータがあります

basicAck(long deliveryTag、ブール値の倍数)

deliveryTag:チャネルで配信されたメッセージを識別するために使用されます。RabbitMQがメッセージをコンシューマーにプッシュすると、deliveryTagが添付され、メッセージが確認されたときにコンシューマーがどのメッセージが確認されたかをRabbitMQに通知できます。
RabbitMQは、各チャネルで、各メッセージのdeliveryTagが1から増加することを保証します

multiple = true:メッセージID <= deliveryTagのメッセージはすべて確認されます

myltiple = false:メッセージid = deliveryTagのメッセージが確認されます

メッセージが確認されない場合はどうなりますか?

キュー内のメッセージがコンシューマーに送信され、コンシューマーがメッセージを確認しない場合、メッセージは確認されるまでキューに残ります。
コンシューマーAに送信されたメッセージが確認されていない場合、rabbitmqは、コンシューマーAとrabbitmqの間の接続が中断された場合にのみ、コンシューマーAの未確認メッセージを別のコンシューマーに再配信することを検討します。

SpringBootでのメッセージ確認の方法

AcknowledgeMode列挙クラスで定義されている3つの方法があります

RabbitMQはどのようにしてメッセージの信頼できる配信を保証しますか?

メッセージのSpringBootのデフォルトのackモードはAUTOです。

実際のシナリオでは、通常、手動で確認します。

application.yamlの設定が次のように変更されます

spring:
  rabbitmq:
    host: myhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual # 手动ack,默认为auto

対応する消費者コードはに変更されます

@Slf4j
@Component
public class LogListenerManual {

    /**
     * 接收info级别的日志
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = "${log.info.queue}", durable = "true"),
                    exchange = @Exchange(value = "${log.exchange}", type = ExchangeTypes.TOPIC),
                    key = "${log.info.binding-key}"
            )
    )
    public void infoLog(Message message, Channel channel) throws Exception {
        String msg = new String(message.getBody());
        log.info("infoLogQueue 收到的消息为: {}", msg);
        try {
            // 这里写各种业务逻辑
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        }
    }
}

上記で使用した注釈は次のとおりです

RabbitMQはどのようにしてメッセージの信頼できる配信を保証しますか?

JavaConfigに基づく

アノテーションの使用はとても便利なので、なぜJavaConfigが必要なのですか?
JavaConfigは、複数の仮想ホストを同時に構成するなど、さまざまな属性をカスタマイズするのに便利です。

特定のコードについてはGitHubを参照してください

RabbitMQはどのようにしてメッセージの信頼できる配信を保証しますか

メッセージは多くの場合、次の段階を経ます

[画像のアップロードに失敗しました...(image-555f54-1603419542750)]

ここに画像の説明を挿入

したがって、メッセージの信頼性の高い配信を保証するには、これら3つの段階の信頼性の高い配信を保証するだけで済みます。

生産段階

この段階での信頼性の高い配信は、主にConfirmListener(パブリッシャー確認)とReturnListener(失敗通知)に依存します。
前述のように、RabbitMQでのメッセージのフローは、
プロデューサー-> rabbitmqブローカークラスター->交換->キュー->コンシューマーです。

ConfirmListenerは、メッセージがプロデューサーからブローカーに送信されたかどうかを取得できます。ReturnListenerは
、交換からキューにルーティングされていないメッセージを取得できます。

Spring BootStarterのAPIを使用して効果を実証します

application.yaml

spring:
  rabbitmq:
    host: myhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual # 手动ack,默认为auto

log:
  exchange: log.exchange
  info:
    queue: info.log.queue
    binding-key: info.log.key

出版社はコールバックを確認します

@Component
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback {

    @Autowired
    private MessageSender messageSender;

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String msgId = correlationData.getId();
        String msg = messageSender.dequeueUnAckMsg(msgId);
        if (ack) {
            System.out.println(String.format("消息 {%s} 成功发送给mq", msg));
        } else {
            // 可以加一些重试的逻辑
            System.out.println(String.format("消息 {%s} 发送mq失败", msg));
        }
    }
}

失敗通知コールバック

@Component
public class ReturnCallback implements RabbitTemplate.ReturnCallback {

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        String msg = new String(message.getBody());
        System.out.println(String.format("消息 {%s} 不能被正确路由,routingKey为 {%s}", msg, routingKey));
    }
}


@Configuration
public class RabbitMqConfig {

    @Bean
    public ConnectionFactory connectionFactory(
            @Value("${spring.rabbitmq.host}") String host,
            @Value("${spring.rabbitmq.port}") int port,
            @Value("${spring.rabbitmq.username}") String username,
            @Value("${spring.rabbitmq.password}") String password,
            @Value("${spring.rabbitmq.virtual-host}") String vhost) {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(vhost);
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
                                         ReturnCallback returnCallback, ConfirmCallback confirmCallback) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setReturnCallback(returnCallback);
        rabbitTemplate.setConfirmCallback(confirmCallback);
        // 要想使 returnCallback 生效,必须设置为true
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }
}

ここでRabbitTemplateのパッケージを作成しました。主なことは、送信時にメッセージIDを追加し、メッセージIDとメッセージの対応を保存することです。RabbitTemplate.ConfirmCallbackはメッセージIDのみを取得でき、メッセージの内容は取得できないためです。したがって、独自のSave thismappingリレーションシップが必要です。信頼性の要件が高い一部のシステムでは、このマッピング関係をデータベースに保存し、正常に送信してマッピング関係を削除し、失敗した場合は常に送信できます。

@Component
public class MessageSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public final Map<String, String> unAckMsgQueue = new ConcurrentHashMap<>();

    public void convertAndSend(String exchange, String routingKey, String message) {
        String msgId = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(msgId);
        rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
        unAckMsgQueue.put(msgId, message);
    }

    public String dequeueUnAckMsg(String msgId) {
        return unAckMsgQueue.remove(msgId);
    }

}

テストコードは

@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgProducerTest {

    @Autowired
    private MessageSender messageSender;
    @Value("${log.exchange}")
    private String exchange;
    @Value("${log.info.binding-key}")
    private String routingKey;

    /**
     * 测试失败通知
     */
    @SneakyThrows
    @Test
    public void sendErrorMsg() {
        for (int i = 0; i < 3; i++) {
            String message = "this is error message " + i;
            messageSender.convertAndSend(exchange, "test", message);
        }
        System.in.read();
    }

    /**
     * 测试发布者确认
     */
    @SneakyThrows
    @Test
    public void sendInfoMsg() {
        for (int i = 0; i < 3; i++) {
            String message = "this is info message " + i;
            messageSender.convertAndSend(exchange, routingKey, message);
        }
        System.in.read();
    }
}

最初にテスト失敗の通知に来てください

出力は

消息 {this is error message 0} 不能被正确路由,routingKey为 {test}
消息 {this is error message 0} 成功发送给mq
消息 {this is error message 2} 不能被正确路由,routingKey为 {test}
消息 {this is error message 2} 成功发送给mq
消息 {this is error message 1} 不能被正确路由,routingKey为 {test}
消息 {this is error message 1} 成功发送给mq

メッセージはブローカーに正常に送信されますが、キューにルーティングされません

出版社の確認をテストしてみましょう

出力は

消息 {this is info message 0} 成功发送给mq
infoLogQueue 收到的消息为: {this is info message 0}
infoLogQueue 收到的消息为: {this is info message 1}
消息 {this is info message 1} 成功发送给mq
infoLogQueue 收到的消息为: {this is info message 2}
消息 {this is info message 2} 成功发送给mq

メッセージはブローカーに正常に送信され、キューにも正常にルーティングされます

保管段階

この段階では高可用性については検討していませんが、結局のところ、クラスターは運用と保守によって構築されています。時間があれば、この簡単なコンテンツを追加します。

消費段階

消費段階での信頼性の高い配信は、主にackによって保証されます。
前回の記事では、ネイティブapiackメソッドとSpringBootフレームワークackメソッドを紹介しました

全体として、実稼働環境では、通常、単一の手動ackを使用します。消費が失敗した後、キューに再入することはありません(再び失敗する可能性が高いため)が、メッセージを再配信します。将来のトラブルシューティングを容易にするために、デッドレターキューに移動します。

さまざまな状況を要約する

  1. メッセージは確認後にブローカーから削除されます
  2. nackまたはrejectの後、次の2つの状況に分けられます
    (1)reque = true、メッセージはキューに再入力されます(2)reque = fasle、メッセージは直接破棄されます。指定すると、デッドレターキューに配信されます

おすすめ

転載: blog.csdn.net/m0_46657043/article/details/109237163