springboot+rabbitmq两小时入门(七):生产者发送失败和消费者消费失败处理

消息队列经常会发送失败和消费失败,这两种问题在日常工作中是不可忽视的。

消息发送失败情况:

1、网络抖动导致生产者和mq之间的连接中断,导致消息都没发。

答:rabbitmq有自动重连机制,叫retry。具体到rabbitTemplate中叫retryTemplate,可以通过设置retryTemplate来设置重连次数。

         1.1、到了重连次数了,还是没连上怎么办呢?造成这种情况通常是服务器宕机等环境问题,这时候会报AmqpException,我们可以捕获这个异常,然后把消息存入缓存中。等环境正常后,做消息补发。

2、消息发了但是mq没收到,或者mq收到了但是进入到交换机之前(如果开启了消息持久化,那则是持久化之前。交换机、队列、消息默认都是持久化的)消息丢了。

答:rabbitmq有confirm机制,即mq收到消息后会发送一个叫ack的标识给生产者,ack为true表示收到了,ack为false表示没收到或丢了。rabbitTemplate中有confirmCallback,在这个callback里把ack为false的消息存到缓存,用另外线程重发。

3、消息到交换机了,但是找不到对应的queue。

答:rabbitmq有return机制,在rabbitTemplate中有returnCallback。找不到queue的消息都会进入到这个callback,在这个callback里把消息存到缓存,用另外线程重发。

消费失败情况:

消费失败也有ack机制,和生成者ack不同。我们根据处理结果返回ack(确认收到)、nack(确认未收到)、reject(拒绝)(需开启手动ack模式)。mq收到是ack的话会把消息从mq中剔除,不剔除的话mq会不断重试。

1、网络抖动、消费者代码异常、数据异常。

答:在消费者catch块里返回nack,返回nack之前还要做计数。达到规定的次数后将消息存到缓存并返回ack(因为消费者代码异常、数据异常导致的消费失败重试多少次都成功不了,不处理的话会死循环的)。

application.properties配置:

spring.rabbitmq.host=localhost

# TCP/IP端口为5672,http端口为15672
spring.rabbitmq.port=5672

spring.rabbitmq.username=root

spring.rabbitmq.password=root

# 开启发送确认
spring.rabbitmq.publisher-confirms=true

# 开启发送失败退回
spring.rabbitmq.publisher-returns=true

# 消费者ack有3种模式:NONE、AUTO、MANUAL
# NONE: 不管消费是否成功mq都会把消息剔除,这是默认配置方式。
# MANUAL:手动应答
# AUTO:自动应答,除非MessageListener抛出异常。
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual

生产者:

package com.example.rabbitmq;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RabbitMQController {
    
    // 这里用的是RabbitTemplate发消息,也可以用AmqpTemplate,推荐使用RabbitTemplate。
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping(value = "/helloRabbit5")
    public String sendMQ5(){
        String msg = "rabbitmq生成者发送失败和消费失败处理方案";
        try {
            // 针对网络原因导致连接断开,利用retryTemplate重连10次
            RetryTemplate retryTemplate = new RetryTemplate();
            retryTemplate.setRetryPolicy(new SimpleRetryPolicy(10));
            rabbitTemplate.setRetryTemplate(retryTemplate);

            // 确认是否发到交换机,若没有则存缓存,用另外的线程重发,直接在里面用rabbitTemplate重发会抛出循环依赖错误
            rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
                if (!ack) {
                    // 存缓存操作
                    System.out.println(msg + "发送失败:" + cause);
                }
            });

            // 确认是否发到队列,若没有则存缓存,然后检查exchange, routingKey配置,之后重发
            rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
                // 存缓存操作
                System.out.println(new String(message.getBody()) + "找不到队列,exchange为" + exchange + ",routingKey为" + routingKey);
            });

            rabbitTemplate.convertAndSend("myExchange1", "routingKey4", msg);
        } catch (AmqpException e) {
            // 存缓存操作
            System.out.println(msg + "发送失败:原因重连10次都没连上。");
        }

        return "success";
    }
}

消费者:

package com.example.rabbitmq;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

@Component
public class Receiver {

    /**
     * basicNack(long deliveryTag, boolean multiple, boolean requeue)
     * deliveryTag: 每条消息在mq内部的id,
     * multiple: 是否批量(true:将一次性拒绝所有小于deliveryTag的消息);
     * requeue: 是否重新入队
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = "myQueue6"),
                    exchange = @Exchange(value = "myExchange1"),
                    key = "routingKey4"
            ))
    public void process7(Message message, Channel channel) throws Exception {
        // 模拟消费者代码异常,这种情况必须在catch块设置重试次数(也可以在配置文件中全局设置重试次数,当然百度的方案都不行,所以我没成功过),防止死循环
        // catch块中重试可用redis的自增来做计数器,从而控制重试次数
        try {
            int i = 1/0;
        } catch (Exception e) {
            System.out.println("myQueue6:" +  new String(message.getBody()));
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            // 达到重试次数后用这行代码返回ack,并将消息存缓存
            // channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
}

启动项目,访问http://localhost:8080/helloRabbit5,当返回nack时会不断打印“myQueue6:rabbitmq生成者发送失败和消费失败处理方案”(因为我这里没有设重试次数),当返回ack时,只打印一次。

  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值