初学RabbitMQ(三),了解回调函数,并手动确认消息进行消费

初学RabbitMQ(二),初识扇形交换机与主题交换机

前面两节,说明了常用的三种交换机(直连交换机、扇形交换机、主题交换机)以及对应的用法。但是实际开发中,可能会需要判断消息主体,来确定该消息需要进行怎样的逻辑确认,从而判断获取到的消息是否需要ACK。

 首先调用回调函数,需要在application.properties中完成相关的配置,在这一系列中,使用的SpringBoot版本是2.4.3

spring.application.name=MqProducer
##配置rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
###配置回调函数确认 ->新增的配置
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirm-type=correlated
##端口
server.port=8961

接下来完成以下对应的配置文件RabbitConfig.java

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);

        //设置强制调用回调函数
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setConfirmCallback((correlationData, b, s) -> {
            System.out.println("相关数据:"+correlationData);
            System.out.println("确认情况:"+b);
            System.out.println("原因:"+s);
        });

        rabbitTemplate.setReturnsCallback(returnedMessage -> {
            System.out.println("交换机为:"+returnedMessage.getExchange());
            System.out.println("返回消息为:"+returnedMessage.getMessage());
            System.out.println("路由键为:"+returnedMessage.getRoutingKey());
            System.out.println("回应消息为:"+returnedMessage.getReplyText());
            System.out.println("回应代码为:"+returnedMessage.getReplyCode());
        });
        return rabbitTemplate;
    }
}

将数据发送到RabbitMQ服务端时,总的会有四种状态:

1.没有找到交换机
2.找到了交换机没有找到队列
3.交换机和队列都没有找到(其实就是第一种情况)
4.成功发送

写一下代码,查看这四种消息状态的返回情况~
第一种状态,随便输入一个交换机的名字就好了:

    @RequestMapping("/noExchange")
    public void sendToNoExchange(){
        String uuid = String.valueOf(UUID.randomUUID());
        String message = "This is a first message to topic : Woman!";
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
        Map<String,Object> m = new HashMap<>();
        m.put("messageId", uuid);
        m.put("createTime",date);
        m.put("message",message);
        rabbitTemplate.convertAndSend("amq.topic1","topic.asdasd",m);
    }

 调用该接口发送消息后,会调用ConfirmCallback回调函数,提示信息404,NOT_FOUNT exchange

相关数据:null
确认情况:false
原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'amq.topic1' in vhost '/', class-id=60, method-id=40)

第二种状态,使用amq.topic但是队列名字随便写:

  @RequestMapping("/noRoute")
    public void sendToNoRoute(){
        String uuid = String.valueOf(UUID.randomUUID());
        String message = "This is a first message to topic : Woman!";
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
        Map<String,Object> m = new HashMap<>();
        m.put("messageId", uuid);
        m.put("createTime",date);
        m.put("message",message);
        rabbitTemplate.convertAndSend("amq.topic","asdasdsad",m);
    }

 调用该接口发送消息后,会调用ConfirmCallback和ReturnsCallback回调函数,提示信息NO_ROUTE

交换机为:amq.topic
返回消息为:(Body:'{createTime=2021-02-24 11:20, messageId=a402a2fe-2bcc-4124-b88e-f7637aae914b, message=This is a first message to topic : Woman!}' MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
路由键为:asdasdsad
回应消息为:NO_ROUTE
回应代码为:312
相关数据:null
确认情况:true
原因:null

第三种状态和第一种状态一致,先找交换机,没有交换机就直接调用回调函数了。

第四种状态正常发送消息到服务端中,调用ConfirmCallback函数

相关数据:null
确认情况:true
原因:null

消费者端,若想要手动确认的话,就需要进行以下配置:

import com.mq.mqcustomer.ack.MyAckListener;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MessageListenerConfig {

    @Autowired
    private CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    private MyAckListener myAckListener;
    @Bean
    SimpleMessageListenerContainer simpleMessageListenerContainer(){
        SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(cachingConnectionFactory);
        listenerContainer.setConcurrentConsumers(1);
        listenerContainer.setMaxConcurrentConsumers(1);
        //设置确认消息的模式,将自动确认改为手动确认,若没有设置此项,则还是自动确认
        listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //设置监听的队列。只有往这些队列中生产消息了,才会进入到下面的消息监听器
        listenerContainer.addQueueNames("directQueue","topic.man");
        listenerContainer.setMessageListener(myAckListener);
        return listenerContainer;
    }
}

现在编写一下消息监听类:

package com.mq.mqcustomer.ack;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MyAckListener implements ChannelAwareMessageListener {

    enum Action{
        ack,resend
    }

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long key = message.getMessageProperties().getDeliveryTag();
        Enum s = null;
        try{
            String msg = message.toString();
            Map<String,Object> m = stringToMap(msg.split("'")[1]);
            Object messageId = m.get("messageId");
            Object createTime = m.get("createTime");
            Object messageData = m.get("message");
            String queue = message.getMessageProperties().getConsumerQueue();
            if(queue.equals("directQueue")){
                System.out.println("接收到队列:"+queue+"的消息!");
                System.out.println("接收到的消息为:"+m.toString());
                System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
            }

            if(queue.equals("topic.man")){
                System.out.println("接收到队列:"+queue+"的消息!");
                System.out.println("接收到的消息为:"+m.toString());
                System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
            }

            s = Action.ack;
        }catch(Exception e){
            e.printStackTrace();
            s = Action.resend;
        }finally{
            if(s == Action.ack){//消息处理正常,就正常消费消息
                channel.basicAck(key,true);
            }else if(s == Action.resend){//若有处理异常,则拒绝消费,并将该消息再放入到队列中
                channel.basicReject(key,true);
            }else{//若出现不明情况,则拒绝消费,并且删除队列中的消息
                channel.basicNack(key,false,false);
            }
        }

    }


    public Map<String,Object> stringToMap(String str){
        str = str.substring(1,str.length()-1);
        String[] messages =  str.split(",");
        Map<String,Object> m = new HashMap<>();
        for (String message : messages) {
            m.put(message.split("=")[0].trim(),message.split("=")[1]);
        }
        return m;
    }
}

这段代码根据不同的队列进行不同的业务逻辑的代码编写,若访问不包含在queueNames中的队列方法,则不会访问该手动确认的监听器。

接收到队列:directQueue的消息!
接收到的消息为:{createTime=2021-02-24 14:13, messageId=c2b2aa6e-cc9c-4292-85c3-658197e862d7, message=This is a first message!}
接收到的消息MessageId:c2b2aa6e-cc9c-4292-85c3-658197e862d7,消息主体为:This is a first message!,接收到的时间为:2021-02-24 14:13
接收到队列:topic.man的消息!
接收到的消息为:{createTime=2021-02-24 14:15, messageId=94d93b8b-79af-4c2f-a9a7-e95b71f2c61a, message=This is a first message to topic : Man!}
接收到的消息MessageId:94d93b8b-79af-4c2f-a9a7-e95b71f2c61a,消息主体为:This is a first message to topic : Man!,接收到的时间为:2021-02-24 14:15

能确认消费,那就能拒绝消费,消费者就是这么任性~

这就是消费者确认机制!

 1.自动确认,RabbitMQ消费后的默认确认模式。只要有消息下达到消费者端,该消息就会被消费,无论是否正确消费。若要拒绝消费,就需要try-catch异常,然后针对异常信息,拒绝该消息的消费。
 2.手动确认:basicAck\basicReject\basicNack三种模式。

basicAck:确认消费,其中有一个参数

  ①deliveryTag,对应的消息通道的名字。

根据消息通道的名称,将消息进行准确的消费。

basicReject:确认拒绝消费,其中有两个参数

  ①deliveryTag,对应的消息通道的名字。
  ②flag,若为true,就会拒绝消费该消息,并将该消息再放回到队列中。若为false,就告诉MQ服务端,我知道了这条消息了, 但是我不想要它,把它删了吧!
  若使用拒绝消费后,将消息放回到队列中,需要注意的是,入队的时机。否则若使用不当的话,就会造成该消息入队-消费-入队-消费的死循环中,增加队列的对长。


basicNack:确认拒绝消费,其中有三个参数

  ①deliveryTag,对应的消息通道的名字。
  ②flag1,批量拒绝,若为true,则会将比当前tagId小的消息全部拒绝。
  ③flag,若为true,就会拒绝消费该消息,并将该消息再放回到队列中。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值