RabbitMQ的常见问题与解决方法

消息可靠性问题

消息可靠性: 消息由生产者发送到MQ, 最终被消费者成功消费.

产生消息丢失的几种情况:

  1. 消息发送到交换机的过程中失败
  2. 消息成功发送到交换机,由交换机路由到队列的过程中失败
  3. MQ宕机, 队列中的消息丢失
  4. 消费者收到了消息, 在消费之前失败.

解决方法

生产者:

  1. 生产者无法连接MQ时, 开启重试策略,确保消息传入MQ,重试次数耗尽则存入数据库, 后期交给人工处理.
    在配置文件中加入下列代码
yaml 复制代码
spring:
 rabbitmq:
   connection-timeout: 1s # 设置MQ的连接超时时间
   template:
     retry:
       enabled: true # 开启超时重试机制
       initial-interval: 1000ms # 失败后的初始等待时间
       multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = 上次等待时长 * multiplier
       max-attempts: 3 # 总共尝试次数
  1. 生产者能够连接MQ, 但消息未到达交换机, 开启confirm机制, 消息到达交换机返回ack, 未到达交换机返回nack.
    配置文件开启confirm机制
yaml 复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
    publisher-returns: true # 开启publisher return机制

下列为测试代码: 当rabbitTemplate.convertAndSend("kk.direct", "red", "hello", cd);中的kk.direct交换机存在,消息发送到交换机则收到ack否则收到nack.

java 复制代码
@Test
void testPublisherConfirm() {
    // 1.创建CorrelationData
    CorrelationData cd = new CorrelationData();
    // 2.给Future添加ConfirmCallback
    cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
        @Override
        public void onFailure(Throwable ex) {
            // 2.1.Future发生异常时的处理逻辑,基本不会触发
            log.error("send message fail", ex);
        }
        @Override
        public void onSuccess(CorrelationData.Confirm result) {
            // 2.2.Future接收到回执的处理逻辑,参数中的result就是回执内容
            if(result.isAck()){ // result.isAck(),boolean类型,true代表ack回执,false 代表 nack回执
                log.debug("发送消息成功,到达交换机,收到 ack!");
            }else{ // result.getReason(),String类型,返回nack时的异常描述
                log.error("发送消息失败,没到交换机,收到 nack, reason : {}", result.getReason());
                // 将发送失败的消息存储到数据库,将来尝试重发
            }
        }
    });
    // 3.发送消息
    rabbitTemplate.convertAndSend("kk.direct", "red", "hello", cd);
}
  1. 消息到交换机但是没有到队列, 开启return机制,保证消息到达队列.
    定义一个配置类开启return机制
java 复制代码
package com.itheima.publisher.config;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Slf4j
@AllArgsConstructor
@Configuration
public class MqConfig {
   private final RabbitTemplate rabbitTemplate;

   @PostConstruct
   public void init(){
       rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
           @Override
           public void returnedMessage(ReturnedMessage returned) {
           //路由到队列失败才执行到这里
               log.error("触发return callback,");
               log.debug("exchange: {}", returned.getExchange());
               log.debug("routingKey: {}", returned.getRoutingKey());
               log.debug("message: {}", returned.getMessage());
               log.debug("replyCode: {}", returned.getReplyCode());
               log.debug("replyText: {}", returned.getReplyText());
           }
       });
   }
}

测试代码同上.

  1. 发送消息失败,如在ConfirmCallback中收到nack,ReturnCallBack异常, 可以将消息记录到失败消息表,由定时任务进行发布,每隔10秒钟(可设置)执行获取失败消息重新发送,发送一次则在失败次数字段加一,达到3次停止自动发送由人工处理。
消费者

消费者开启自动ack,当成功处理消息后向MQ发送ack,MQ从队列中删除消息,否则发送nack,MQ再次投递消息给消费者,如果是消息处理或校验异常 ,自动返回reject, 消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息.

这样做之后若是消费者出现异常,消息会不断被requeue到队列,可能会导致导致mq的消息处理飙升,带来不必要的压力. 可以设置本地重试策略,当本地重试次数耗尽后, 将失败消息路由到错误队列.

本地重试策略配置:

yaml 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 1000ms # 初识的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = 上次等待时长 * multiplier
          max-attempts: 3 # 最大重试次数

最后监听错误队列,把消息写入数据库,后期交由人工处理.

持久化操作

创建交换机、队列、消息时设置持久化,保证MQ宕机后重启不丢失.

交换机持久化:

队列持久化:

消息持久化:

消费积压问题

原因:

生产者生产消息的速度 远高于 消费者消费消息的速度,于是就会造成消息积压在MQ.

解决方法
  1. 可以采用惰性队列先将生产的消息存入磁盘, 缓解队列储存消息的压力, 在消费时再加载到内存.
    不过这种方法可能会导致磁盘空间爆满.
  2. 先分析消息积压的原因, 大概率消费者出问题,首先修复消费者代码,开启消费者程序,临时开启多个消费者,来以倍速消费积压的消息。当积压的消息消费的差不多的情况,关闭临时消费者.

消息重复消费问题

也是幂等性问题.

幂等性:在程序开发中,是指同一个业务,执行一次或多次对业务状态的影响是一致的. 如根据id删除数据,查询数据.

可是数据的更新往往不是幂等的,如果重复执行可能造成不一样的后果,如取消订单,恢复库存的业务。如果多次恢复就会出现库存重复增加的情况; 退款业务。重复退款对商家而言会有经济损失。

因此,我们必须想办法保证消息处理的幂等性.

解决方法

使用redis来实现

消费者先从redis缓存中获取消息ID,如果消息ID存在则说明有消费者正在消费该数据,则不执行任何操作, 若消息ID不存在,则说明这是第一次消费该数据,则正常消费,消费之后再redis中保存一个消息ID,并设置过期时间.

相关推荐
程序员白话4 小时前
使用kube-prometheus在K8s集群快速部署Prometheus+Grafana
后端·数据可视化
dl7434 小时前
spirng事务原理
后端
往事随风去4 小时前
Redis的内存淘汰策略(Eviction Policies)有哪些?
redis·后端·算法
秦禹辰4 小时前
宝塔面板安装MySQL数据库并通过内网穿透工具实现公网远程访问
开发语言·后端·golang
lypzcgf4 小时前
Coze源码分析-资源库-删除插件-后端源码-应用和领域服务层
后端·go·coze·coze插件·coze源码分析·智能体平台·ai应用平台
lssjzmn4 小时前
Spring Web 异步响应实战:从 CompletableFuture 到 ResponseBodyEmitter 的全链路优化
java·前端·后端·springboot·异步·接口优化
shark_chili4 小时前
程序员必知的底层原理:CPU缓存一致性与MESI协议详解
后端
愿时间能学会宽恕5 小时前
SpringBoot后端开发常用工具详细介绍——SpringSecurity认证用户保证安全
spring boot·后端·安全
CodeSheep5 小时前
稚晖君又开始摇人了,有点猛啊!
前端·后端·程序员