RabbitMqのメッセージ確認の仕組みとRabbitMqチャット機能への応用

1.メッセージの損失の理由

1. 送信者がメッセージを送信すると、メッセージが失われます. メッセージが送信されたように見えます
. 2. MQ はメッセージを受信しましたが、消費時にコンシューマーはそれを受信して​​いません.

ここに画像の説明を挿入
p->b e->q および q->c の 3 つの場所で、確実に到達できるようにします (Shang Silicon Valley からの写真)。

2.本番側のコールバック機能と設定について

#开启发送端确认
spring.rabbitmq.publisher-confirm-type=correlated
#开启发送端消息抵达队列的确认
spring.rabbitmq.publisher-returns=true
#只要抵达队列 以异步方式优先回调returnconfirm
spring.rabbitmq.template.mandatory=true


@Slf4j
@Configuration
public class MyRabbitConfig {
    
    
    @Autowired
    RabbitTemplate rabbitTemplate;
 
    @Bean
    public MessageConverter messageConverter(){
    
    
        return new Jackson2JsonMessageConverter();
    }
 
    /**
     *定制RabbitTemplate
     * 1.服务收到消息就回调
     *    1)spring.rabbitmq.publisher-confirms=true
     *    2)设置确认回调confirmCallback
     * 2.消息正确抵达队列进行回调
     *    1)spring.rabbitmq.publisher-returns=true
     *    2) spring.rabbitmq.template.mandatory=true
     *    设置确认回调ReturnCallback
     * 3.消费端确认(保证每个消费被正确消费,此时才可以broker删除这个消息)
     *    1)默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
     *       问题:
     *           我们收到很多消息,自动回复服务器ack,只有一个消息处理成功,宕机了,就会发生消息丢失。
     *           消费者手动确认模式,只要我们没有明确告诉MQ,货物被签收,没有ACK
     *           消息就一直是unacked状态,即使Consumer宕机。消息不会丢失,会重新变成ready
     *   2)如何签收:
     *      channel.basicAck(deliveryTag,false);签收获取
     *      channel.basicNack(deliveryTag,false,true);拒签
     *
     */
 
    @PostConstruct   //再配置类对象创建完成以后,执行这个方法
    public void initRabbitTemplate(){
    
    
        RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
    
    
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                if (ack){
    
    
                    System.out.println("发送成功");
                }else {
    
    
                    System.out.println("发送失败");
                }
            }
        };
        rabbitTemplate.setConfirmCallback(confirmCallback);
 
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    
    
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
            }
        });
    }
 
 
}

(上記のコードは Shang Silicon Valley のものです)

3. コンシューマー側の ack モードとチャット関連のコード

springboot-rabbit は 3 つのメッセージ確認モードを提供します:
AcknowledgeMode.NONE: 確認なしモード (プログラムが異常であるかどうかに関係なく、監視メソッドが実行されている限り、メッセージは消費されます。rabbitmq での自動確認に相当します。 、およびこのメソッドは推奨されません)
AcknowledgeMode .AUTO: 自動確認モード (デフォルトでは、消費者は例外がない場合は自動的に確認し、例外がある場合は確認せず、無限に再試行し、その結果、ウサギの自動確認と混同しないでください) AcknowledgeMode.MANUAL: 手動確認モード (
手動で channel.basicAck を呼び出して確認する必要があります。例外をキャッチして再試行回数を制御し、失敗メッセージの処理を制御することもできます)

通常は、手動確認モードを使用することをお勧めします。例は
のとおりです。

確認モード: なし、自動、手動
なし: 自動確認; 自動: 状況に応じて; 手動: 手動確認

spring:
  rabbitmq:
    host: localhost
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual  // 开启手动确认
        retry:
          enabled: true  // 开启重试
          max-attempts: 3  //最大重试次数
          initial-interval: 2000ms  //重试间隔时间
@RestController
@RequestMapping("/mq")
//@Api(tags = "mq相关接口" , description = "MqController | 消息模块")
public class MqController {
    
    
    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private ProdServer prodServer;
    @Autowired
    WebSocketServer webSocketServer;

    @RabbitListener(queues = {
    
    FanoutRabbitConfig.DEFAULT_BOOK_QUEUE})
    public void listenerAutoAck(String text, Message message, Channel channel) {
    
    

        // TODO 如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
    
    
            logger.info("[消费者一监听的消息] - [{}]", text);
            ChatDto.MqChat chatDto = JSONObject.parseObject(text, ChatDto.MqChat.class);
            Integer rs = WebSocketServer.sendInfo(chatDto.getMessage(), chatDto.getToUserId());
//            yanUserChatService.saveChat(chatDto.getOpenid(),chatDto,rs);
//            webSocketServer.sendMqMessage(text);

            // TODO 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
    
    
            try {
    
    
                // TODO 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
    
    
                e1.printStackTrace();
            }
        }
    }
}

 	@ApiOperation("聊天核心方法 ")
    public void defaultMessage(@RequestBody ChatDto chatDto,
                               HttpServletRequest request)  throws IOException{
    
    

        String message = chatDto.getContent();
        String toUserId = chatDto.getEmail();
        String openid = Util.fromRequestToOpenid(request);
        Map<String, String> map = new HashMap<String, String>();
        map.put("message",message);
        map.put("toUserId",toUserId);
        map.put("openid",openid);
        rabbitTemplate.convertAndSend("fanoutExchange", "", JSONObject.toJSONString(map));
    }

関連する websocket のコード

 public static Integer sendInfo(String message,@PathParam("userId") String userId) throws IOException {
    
    
        log.info("发送消息到:"+userId+",报文:"+message);
        if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
    
    
            //这个地方最好封装一下报文请求 要不然前端不好处理
            JSONObject jsonObject =new JSONObject();
            //追加发送人(防止串改)
            jsonObject.put("type","receive");
            jsonObject.put("content",message);
            webSocketMap.get(userId).sendMessage(jsonObject.toJSONString());

            log.info("发送消息到成功");
            return 1;
        }else{
    
    
            log.error("用户"+userId+",不在线!");
            return 0;
        }
    }

チャット状況下での rabbitmq のアプリケーション; メッセージの送信後にビジネス ロジックを切り離し、チャット プロセス中のパフォーマンスを向上させます。

4. メッセージの信頼性を確保する方法

1. メッセージの紛失

メッセージが送信されると、ネットワークの問題によりサーバーに到達しない
. フォールト トレラントな方法 (try-catch) を作成する. メッセージの送信時にネットワーク障害が発生する可能性がある. 障害後に再試行メカニズムが必要である. , これはデータベースに記録できます.
定期的なスキャンと再送信によって行う必要があります.,定期的にデータベースに移動して、失敗したメッセージをスキャンして再送信します. メッセージがブローカーに到達すると、ブローカーはメッセージをディスクに書き込みます (持続性) 成功と見なされます. この時点で、Broker はまだ永続化を完了しておらず、ダウンしています。パブリッシャーは、成功したメッセージを確認し、データベース メッセージのステータスを変更するための確認コールバック メカニズムも追加する必要があります。自動ACKの状態。コンシューマーはメッセージを受信しますが、メッセージを受信する時間がなく、シャットダウンします. 手動 ACK を有効にする必要があります. コンシューマーは、コンシュームが成功した場合にのみ削除されます. 失敗したか、処理する時間がなかった場合、noAck になり、キューに再入力します。





2. 繰り返されるメッセージ

メッセージの消費が成功し、トランザクションが送信され、ACK が発生するとマシンがダウンします。その結果、ACK の成功はなく、ブローカーのメッセージは unack から ready に変わり、他のコンシューマーに送信されます.
メッセージの消費は失敗します. 再試行メカニズムにより、メッセージは自動的に送信され、正常に消費されます
. ack がダウンし、メッセージが unack から ready に変わり、ブローカーが
コンシューマーを再送信する ビジネス消費インターフェースはべき等になるように設計する必要があります。例えば、インベントリーに作業指示書のステータスフラグがある場合
、重複防止テーブル (redis/mysql) を使用し、送信された各メッセージにはビジネス固有の識別子があり、処理後、処理する必要はありません。 rabbitMQ の
メッセージ. 各メッセージには、配信されたかどうかを取得するために使用できる redelivered フィールドがあります. 再投稿, 初回ではありません

3. 未処理のニュース

コンシューマーのダウンタイムのバックログコンシューマー
の消費容量が不十分
送信者がトラフィックを送信しすぎている
通常の消費のためにオンラインになるコンシューマーが増える 専用のキュー消費サービスでオンラインになり、最初
にバッチでメッセージを取得し、データベースに記録して処理しますゆっくりオフライン
—— ——————————————
元のテキストリンクの第 4 部: https://blog.csdn.net/weixin_40566934/article/details/119643740

5. rabbitmq デッドレターキューと遅延キューの使用

———————————————
元のリンク: https://blog.csdn.net/fu_huo_1993/article/details/88350188

おすすめ

転載: blog.csdn.net/qq_21561833/article/details/120253963
おすすめ