对于消息中间件来讲,能解决我们实际场景中好多问题,但是也存在一些弊端:消息丢失与重发,rabbitmq自身提供了解决消息的丢失和重发,下来我们就来分析消息是怎么丢失和重发的。

1.消息未到达交换机
当我们发送一条消息的时候,消息首先被发到叫唤机,如未给当前队列设定交换机则发送到默认的交换机,假设此时发送失败,那么消息就丢失了。

2.消息未到达队列
发送的交换机未绑定队列,或者绑定的队列已满的情况下,交换机发送消息到队列会失败,此时消息丢失。

3.消费者消费失败
由于rabbitmq默认是自动ack,只要消息到达消费者就自动向队列返回ack,那么此时业务处理失败,我们可能需要重试操作,此时此条消息也不在队列。

解决以上问题,我们首先需要将rabbitmq设置成手动ack,以下是mq的配置

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    publisher-confirms: true #开启发送端确认机制
    publisher-returns: true # 开启returns
    template:
      mandatory: true # 交换机没有匹配的队列时,会将消息返回给生产者,避免消息丢失
    listener:  
      # 开启ACK
      simple:
        acknowledge-mode: manual #手动确认模式
        prefetch: 1 #每次从队列中取一个,轮询分发,默认是公平分发
        default-requeue-rejected: true #默认重新排队被拒绝
        #失败后重试
        retry:
            enabled: true #开启
            max-attempts: 5 #最大尝试次数
            initial-interval: 3000 #初始间隔

怎么确认消息未到达交换机呢?我们需要实现ConfirmCallback的confirm方法,就可以确认,那么在发送失败的情况下,我们就可以做一些操作

@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		log.info("===============ack=========" + JSONObject.toJSONString(correlationData) + " 是否成功:" + ack + " error "
				+ cause);
		if (ack) {
			// 已发送到交换机
			log.info("==========发送到交换机啦======");
		} else {
			// 未发送到交换机
			log.info("==========发送到交换机失败啦======");
		}
	}

怎么确认消息未到达队列呢?同样我们可以实现ReturnCallback的returnedMessage方法,当消息未送达队列的时候,会调用此方法,我们可以将失败的消息存储起来定时补发。

@Override
	public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
		/*消息到达交换机,没有路由到队列,根据订单号查询到订单数据,并将数据保存到异常消息表中,定时补发,并报警人工处理
		 * 1 交换机没有绑定队列 
		 * 2 交换机根据路由键没有匹配到队列 
		 * 3 队列消息已满
		 */
		// 交换机未发送到队列
		log.info("==========发送到队列失败啦======");
		log.info("replyCode =" + replyCode + " replyText = " + replyText + " exchange = " + exchange + " routingKey = "
				+ routingKey);

	}

当消费者消费失败的时候我们,我们往往不能就此罢休,可能需要做一些补救措施,延时执行或者重试机制,避免数据的不一致性。消费失败,
1.我们可以拒绝消息,不在入队,此时如果配置了死信队列,该消息将会进入死信队列。
2.也可以将消息重新入队消费。

@RabbitHandler
	@RabbitListener(queues = QueueConstant.NO_QUEUE)
	public void ackReceiver(Channel channel, Message message) {
		try {
			// 模拟执行任务
			Thread.sleep(1000);
			// 模拟异常
			/*
			 * String is = null; is.toString();
			 */
			// 确认收到消息,false只确认当前consumer一个消息收到,true确认所有consumer获得的消息
			//channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
		} catch (Exception e) {
			if (message.getMessageProperties().getRedelivered()) {
				log.info("消息已重复处理失败,拒绝再次接收" + "dd");

				try {
					// 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
					channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			} else {
				log.info("消息即将再次返回队列处理" + "dd");
				try {
					// requeue为是否重新回到队列,true重新入队
					channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			// e.printStackTrace();
		}
	}

如有不明白的童鞋,可评论或者私信,如有不妥之处,请指出,共勉。