SpringBoot整合RabbitMQ 回调函数 direct交换机、fanout交换机、topic交换机

移步 查看
SpringBoot整合RabbitMQ direct交换机、fanout交换机、topic交换机

回调函数

接下来,来看一下回调函数以及针对不同的队列使用不同的逻辑进行数据的消费。

若想要进行函数的回调,那么就需要在application.properties文件中,加入这两句代码

###配置回调函数确认
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirm-type=correlated

然后在生产者项目中配置一下RabbitConfig

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);
         	System.out.println("===============================");
        });

        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());
         	System.out.println("===============================");
        });
        return rabbitTemplate;
    }
}

那么发送数据时,什么时候会进入不同的回调函数呢?
上面已经介绍了四种状态,点击链接直接抵达~
然后添加以下三个方法,匹配了1、2、3中状态。

    @RequestMapping("/noExchange")
    public void sendToNoExchange(){
    	//交换机不存在
        Map<String,Object> m = commonMap("Judge the exchange is not exist!");
        rabbitTemplate.convertAndSend("amq.topic1","topic.asdasd",m);
    }

    @RequestMapping("/noRoute")
    public void sendToNoRoute(){
		//交换机存在但是队列不存在
        Map<String,Object> m = commonMap("Judge the route is not exist!");
        rabbitTemplate.convertAndSend("amq.topic","asdasdsad",m);
    }

    @RequestMapping("/allNo")
    public void sendToAllNo(){
    	//交换机与队列都不存在的情况
        Map<String,Object> m = commonMap("Judge the exchange or route is not exist!");
        rabbitTemplate.convertAndSend("amq.topic1","asdasdsad1",m);
    }

若调用/noExchange接口,会调用ConfirmCallback回调函数:

相关数据: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)

若调用/noRoute接口,会调用ConfirmCallback、ReturnsCallback回调函数:

相关数据:null
确认情况:true
原因:null
===============================
交换机为:amq.topic
返回消息为:(Body:'{createTime=2021-02-25 14:46, messageId=54986dfb-c7e3-4f4b-94a3-ff3882bdef3d, message=Judge the route is not exist!}' MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
路由键为:asdasdsad
回应消息为:NO_ROUTE
回应代码为:312
===============================

若调用/allNo的接口的话,会发现和调用/noExchange的结果是一致的,这边就不说了。

但是若正常发送数据,则会调用ConfirmCallback回调函数:

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

这时候,突然来了个需求,需要针对主题交换机的队列进行不同的逻辑处理?其他交换机的数据都不进行消费。若是直连交换机则拒绝消费并将数据再放入到队列中。若是扇形交换机直接拒绝消费,并删除数据。

这一块有两个内容,1.手动确认消费数据。2.监听队列,判断是否是主题交换机绑定的队列

首先,需要在消费者项目中进行配置,代码中有注释了:

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);
        //设置通同时存在的用户数为1个
        listenerContainer.setConcurrentConsumers(1);
        //设置通同时存在的最大用户数为1个
        listenerContainer.setMaxConcurrentConsumers(1);
        //设置确认消息的模式,将自动确认改为手动确认
        listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        //设置监听的队列
        listenerContainer.addQueueNames("directQueue",
        "firstFanoutQueue","firstTopicQueue");
        //绑定消息自定义消息确认监听器myAckListener
        listenerContainer.setMessageListener(myAckListener);
        return listenerContainer;
    }
}

编写消息确认监听器代码myAckListener:

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,delete
    }

	int count = 0;
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long key = message.getMessageProperties().getDeliveryTag();
        Enum s = null;
        try{
            String msg = message.toString();
            //看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("接收直连交换机中的数据!");
                System.out.println("接收到队列:"+queue+"的消息!");
                System.out.println("接收到的消息为:"+m.toString());
                System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
                s = Action.resend;
                if(count%2!=0){
                	s = Action.ack;
                	System.out.println(queue+"的数据我要消费了!");
                }
				++count;
            }

            if(queue.contains("topic")){
                System.out.println("接收主题交换机中的数据!");
                System.out.println("接收到队列:"+queue+"的消息!");
                System.out.println("接收到的消息为:"+m.toString());
                System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
                s = Action.ack;
            }

            if(queue.contains("fanout")){
                System.out.println("接收扇形交换机中的数据!");
                System.out.println("接收到队列:"+queue+"的消息!");
                System.out.println("接收到的消息为:"+m.toString());
                System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
                s = Action.delete;
            }
        }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;
    }
}

接口调用

调用发送数据到直连交换机的接口,MQ中就有一条数据未确认:
在这里插入图片描述
然后会执行到直连交换机的逻辑代码,并设置为拒绝消费,然后将数据放回队列中,count+1(否则会一直处于消费-拒绝-消费-拒绝的死循环中,导致了队列中的消息积压)。

接收直连交换机中的数据!
接收到队列:directQueue的消息!
接收到的消息为:{createTime=2024-02-25 15:15, messageId=61cc56c7-fd66-444e-84ae-fb0557d42499, message=This is a first message!}
接收到的消息MessageId:61cc56c7-fd66-444e-84ae-fb0557d42499,消息主体为:This is a first message!,接收到的时间为:2024-02-25 15:15

第一次拒绝消费后,将count+1,第二次就要进行消费了

接收直连交换机中的数据!
接收到队列:directQueue的消息!
接收到的消息为:{createTime=2024-02-25 15:15, messageId=61cc56c7-fd66-444e-84ae-fb0557d42499, message=This is a first message!}
接收到的消息MessageId:61cc56c7-fd66-444e-84ae-fb0557d42499,消息主体为:This is a first message!,接收到的时间为:2024-02-25 15:15
directQueue的数据我要消费了!

查看MQ服务端,消费成功!
在这里插入图片描述
这段代码使用的是basic.reject(拒绝消费),其中有两个参数:

tagId:是消息通道的名称。
 flag : 若为true,则会将消息再放入到队列中。若为false,则是告诉MQ,我不要这条消息了,删了吧。
 看上面的代码,我加了count变量,用于将放回队列的消息再次进行消费,否则就会陷入死循环中了。始终都在消费这条数据,并且始终都消费不掉。

若调用扇形交换机的接口
会将消息发送到扇形交换机中所有的队列里,但是我只配置监听了firstFanoutQueue,并且把secondFanoutListener监听器注释掉了。

接收扇形交换机中的数据!
接收到队列:firstFanoutQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:37, messageId=ad097199-b8ac-41cd-8725-eaac690946ea, message=This is a second message to Fanout!}
接收到的消息MessageId:ad097199-b8ac-41cd-8725-eaac690946ea,消息主体为:This is a second message to Fanout!,接收到的时间为:2021-02-25 15:37
CustomerFanoutThirdListener接受到的消息为:{createTime=2021-02-25 15:37, messageId=ad097199-b8ac-41cd-8725-eaac690946ea, message=This is a second message to Fanout!}
扇形交换机的消息我不要了!删了吧

看MQ服务端,可以看到firstFanoutQueue的数据被我拒绝消费了,此时没有数据。而secondFanoutQueue的数据,我并没有消费它,所以还存在。
在这里插入图片描述

接收主题交换机中的数据!
接收到队列:firstTopicQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:43, messageId=3992dadc-f7cd-446e-b7c4-46daea7e260f, message=This is a first message to topic : Man!}
接收到的消息MessageId:3992dadc-f7cd-446e-b7c4-46daea7e260f,消息主体为:This is a first message to topic : Man!,接收到的时间为:2021-02-25 15:43

以上代码,可以看出有三种确认消息的方式:
basic.ack、basic.reject、basic.nack
basic.ack :
basic.reject : 拒绝消费消息,可以选择拒绝消费后是否要将消息放回到队列中,还是直接删除。若第二个参数为true,则放回队列中。但是若选择这个模式的话,就需要注意放回队列的时机以及消费数据获取删除数据的时机。否则会该消息会一直处于消费-拒绝-消费-拒绝的道路。
basic.nack : 拒绝消费消息,有三个参数:
  ①deliveryTag,对应的消息通道的名字。
  ②flag,批量拒绝,若为true,则会将比当前tagId小的消息全部拒绝。
  消息通道的名字,会在原来的tag上+1,也就是每个通道的deliveryTag都比上一个大。
  ③flag1,若为true,就会拒绝消费该消息,并将该消息再放回到队列中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值