RabbitMq 消息丢失和消息手动确认处理

1.安装依赖

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

2.配置rabbitmq

  1. 配置文件
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: zytool
    password: 123456
    virtual-host: /
    publisher-returns: true  # 开启发送消息失败返回 对应RabbitTemplate.ReturnCallback接口
    publisher-confirm-type: correlated # publisher-confirms和publisher-returns是对于消息生产端的配置
    listener:
      simple:
        #并发数与最大并发数
        concurrency: 2
        max-concurrency: 2
        #预取数
        prefetch: 6
        acknowledge-mode: manual  #开启手动确认模式(针对于消息消费端)
  1. 配置交换机、队列、以及绑定
@Configuration
@Slf4j
public class RabbitMqConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);            // 指定 ConfirmCallback
        rabbitTemplate.setReturnCallback(this);             // 指定 ReturnCallback
    }

    /**
     * 交换机
     */
    @Bean
    public DirectExchange myDirectExchange() {
        // 参数意义:
        // name: 名称
        // durable: true
        // autoDelete: 自动删除
        return new DirectExchange(MqConstant.NEWSEXCHANGE, true, false);
    }

    /**
     * 队列
     */
    @Bean
    public Queue myDirectQueue() {
        return new Queue(MqConstant.NEWSQUEUE, true);
    }

    /**
     * 绑定
     */
    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(myDirectQueue())
                .to(myDirectExchange())
                .with(MqConstant.NEWSROUTINGKEY);
    }
}

3. 实现消息发送确认

配置类需要实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback

/**
     * 如果消息到达 exchange, 则 confirm 回调, ack = true
     * 如果消息不到达 exchange, 则 confirm 回调, ack = false
     * 需要设置spring.rabbitmq.publisher-confirms=true
     * @param correlationData
     * @param ack
     * @param cause
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息成功到达exchange!");
        } else {
            log.info("消息未能成功到达exchange!{}", correlationData.getReturnedMessage());
            log.info("消息到达Exchange失败原因:{}", cause);
            // 根据业务逻辑实现消息补偿机制。如记录到数据库或者日志里面,通过定时任务再去跑

        }
    }

    /**
     * exchange 到达 queue, 则 returnedMessage 不回调
     * exchange 到达 queue 失败, 则 returnedMessage 回调
     * 需要设置spring.rabbitmq.publisher-returns=true
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.info("消息报文:{}", new String(message.getBody()));
        log.info("消息编号:{}", replyCode);
        log.info("描述:{}", replyText);
        log.info("交换机名称:{}", exchange);
        log.info("路由名称:{}", routingKey);

        // 根据业务逻辑实现消息补偿机制

    }

4. 实现消息消费确认

这里我通过 NewsLister 类来监听队列

@Component
@Slf4j
public class NewsListener {

    @Autowired
    private NewsInfoService newsInfoService;

    @RabbitHandler
    @RabbitListener(queues = "newsDirectQueue")
    public void getNews(Object rabbitObject, Message message, Channel channel) throws InterruptedException, IOException {
        String msg = new String(message.getBody());
        Boolean ret = false;
        try {
            Thread.sleep(1000);
            NewsInfoVO newsInfoVO = JSONObject.parseObject(msg, NewsInfoVO.class);
            long snowId = SnowflakeIdUtil.getSnowId();
            newsInfoVO.setId(snowId + "");
            newsInfoVO.setStatus(CommonConstant.NEWS_SUBMIT);
            newsInfoVO.setCreateBy(666l);
            newsInfoVO.setCreateTime(new Date());
            newsInfoService.save(newsInfoVO);
            ret = true;
        } catch (Exception e) {
            // redelivered = true, 表明该消息是重复处理消息
            Boolean redelivered = message.getMessageProperties().getRedelivered();

            /**
             * 这里对消息重入队列做设置,例如将消息序列化缓存至 Redis, 并记录重入队列次数
             * 如果该消息重入队列次数达到一次次数,比如3次,将不再重入队列,直接拒绝
             * 这时候需要对消息做补偿机制处理
             *
             * channel.basicNack与channel.basicReject要结合越来使用
             */
            try {
                if (redelivered) {
                    /**
                     * 1. 对于重复处理的队列消息做补偿机制处理
                     * 2. 从队列中移除该消息,防止队列阻塞
                     */
                    // 消息已重复处理失败, 扔掉消息
                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝消息
                    log.error("消息[{}]重新处理失败,扔掉消息", msg); //可以记录到数据库
                }

                // redelivered != true,表明该消息是第一次消费
                if (!redelivered) {
                    // 消息重新放回队列
                    channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
                    log.error("消息[{}]处理失败,重新放回队列", msg);
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }

        } finally {
            if (ret) {
                //消息确认Ack
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
                log.info("消息消费正常!");
            } else {
                log.info("消息消费异常!");
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值