Механизм подтверждения сообщения RabbitMq и его применение в функции чата RabbitMq

1. Причины потери сообщения

1. Когда отправитель отправляет сообщение, сообщение теряется. Кажется, что сообщение было отправлено
2. MQ получил сообщение, но потребитель не получил его при потреблении

вставьте сюда описание изображения
В p->b e->q и q->c три места для обеспечения надежного прибытия (картинка из 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. Режим подтверждения на стороне потребителя и код, связанный с чатом

springboot-rabbit предоставляет три режима подтверждения сообщения:
AcknowledgeMode.NONE: режим без подтверждения (независимо от того, является ли программа ненормальной или нет, пока выполняется метод мониторинга, сообщение будет потребляться. Это эквивалентно автоматическому подтверждению в rabbitmq , и этот метод не рекомендуется)
AcknowledgeMode .AUTO: режим автоматического подтверждения (по умолчанию потребитель автоматически подтверждает, если нет исключения, и не будет подтверждать, если есть исключение, бесконечный повтор, что приводит к бесконечному циклу программа. Не путайте с автоматическим подтверждением в кролике) AcknowledgeMode.MANUAL: режим ручного подтверждения (
требуется вручную вызывать channel.basicAck для подтверждения, можно перехватывать исключения для контроля количества повторных попыток и даже управлять обработкой сообщений об ошибках)

Обычно рекомендуется использовать режим ручного подтверждения,
например:
конфигурация springboot

режим подтверждения: нет, автоматический, ручной
нет: автоматическое подтверждение; автоматический: в зависимости от ситуации; ручной: подтверждение вручную

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));
    }

Связанный код веб-сокета

 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) При отправке сообщения может произойти сбой сети После сбоя должен быть механизм повторных попыток , который может быть записан в базу данных, и это должно быть сделано
путем регулярного сканирования и повторной отправки.Записи регулярную повторную отправку.Если сообщение не было отправлено успешно, регулярно обращаться к базе данных, чтобы сканировать неудачное сообщение для повторной отправки.Когда сообщение достигает брокера, брокер записывает сообщение на диск (постоянство) считается успешным. В настоящее время Брокер еще не завершил персистентность и не работает. Издатель также должен добавить механизм обратного вызова подтверждения, чтобы подтвердить успешное сообщение и изменить статус сообщения базы данных. В состоянии автоматического ACK. Потребитель получает сообщение, но не успевает принять сообщение и затем отключается. Должен быть включен ручной ACK, и потребитель будет удален только в случае успешного потребления. Если он не удался или не успел его обработать , он не получит подтверждения и снова войдет в очередь.





2. Повторяющееся сообщение

Получение сообщения прошло успешно, транзакция была отправлена, и когда происходит подтверждение, машина не работает. В результате подтверждения не происходит, и сообщение брокера снова меняется с unack на ready и отправляет его другим потребителям.Прием
сообщения завершается ошибкой.Из-за механизма повторных попыток сообщение автоматически отправляется для
успешного использования.Когда ack не работает, сообщение меняется с unack на ready , Broker повторно отправляет
потребителю интерфейс бизнес-потребления, который должен быть идемпотентным. Например, если в инвентаре есть флаг состояния заказа на работу
, используйте антидупликационную таблицу (redis/mysql), и каждое отправленное сообщение имеет уникальный идентификатор для бизнеса, после обработки нет необходимости обрабатывать Каждое
сообщение rabbitMQ. Каждое сообщение имеет поле доставлено повторно, по которому можно узнать, было ли оно доставлено. Репост, не первый раз

3. Бэклог новостей

Недостаточное время простоя потребителей
Недостаточная мощность потребления
Отправитель отправляет слишком много трафика
Больше потребителей подключается к сети для нормального потребления Подключайтесь к сети с помощью специальной службы потребления очереди ,
сначала получайте сообщения пакетами, записывайте их в базу данных и обрабатывайте их медленно в автономном режиме
———————————————
Четвертая часть оригинальной текстовой ссылки: https://blog.csdn.net/weixin_40566934/article/details/119643740

5. Использование очереди недоставленных сообщений rabbitmq и очереди задержки

———————————————
Исходная ссылка: https://blog.csdn.net/fu_huo_1993/article/details/88350188

Je suppose que tu aimes

Origine blog.csdn.net/qq_21561833/article/details/120253963
conseillé
Classement