SpringBoot整合RabbitMQ与RabbitMQ高级特性

Spring整合RabbitMQ

生产者

  • 创建生产者工程
    在这里插入图片描述

  • 添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.7.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.1.8.RELEASE</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.7.RELEASE</version>
</dependency>
  • 配置整合
    rabbitmq.properties
rabbitmq.host=192.168.1.104
rabbitmq.port=5672
rabbitmq.username=yemuxia
rabbitmq.password=123456
rabbitmq.virtual-host=/yemuxia_vir

spring-rabbitmq-producer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
    默认交换机类型为direct,名字为:"",路由键为队列的名称
    -->
    <!--
        id:bean的名称
        name:queue的名称
        auto-declare:自动创建
        auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
        durable:是否持久化
    -->

    <rabbit:queue id="spring_queue" name="spring_queue"    auto-declare="true"/>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>

    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>

    <!--定义广播类型交换机;并绑定上述两个队列-->
    <rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange"  auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding  queue="spring_fanout_queue_1"  />
            <rabbit:binding queue="spring_fanout_queue_2"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>


    <!-- 定义队列-->
    <rabbit:queue id="spring_direct_queue" name="spring_direct_queue"  auto-declare="true"/>

    <!--
      定义 Routing  路由模式 交互机
    -->
    <rabbit:direct-exchange name="spring_direct_exchange" >
        <rabbit:bindings>
            <!--direct 类型的交换机绑定队列  key :路由key  queue:队列名称-->
            <rabbit:binding queue="spring_direct_queue" key="info"></rabbit:binding>
        </rabbit:bindings>

    </rabbit:direct-exchange>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star"  auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>

    <!--
      声明  topic 类型的交换机
    -->
    <rabbit:topic-exchange id="spring_topic_exchange"  name="spring_topic_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="baiqi.*"  queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="baiqi.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
  • 编写代码发送消息
    添加单元测试类ProducerTest
package com.baiqi;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
    
    

    //1.注入 RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Test
    public void testHelloWorld(){
    
    
        //2.发送消息

        rabbitTemplate.convertAndSend("spring_queue","hello world spring....");
    }


    /**
     * 发送fanout消息
     */
    @Test
    public void testFanout(){
    
    
        //2.发送消息

        rabbitTemplate.convertAndSend("spring_fanout_exchange","","spring fanout....");
    }


    @Test
    public void testDirect(){
    
    
        //2.发送消息

        rabbitTemplate.convertAndSend("spring_direct_exchange","info","spring Direct....");
    }

    /**
     * 发送topic消息
     */
    @Test
    public void testTopics(){
    
    
        //2.发送消息

        rabbitTemplate.convertAndSend("spring_topic_exchange","baiqi.hehe.haha","spring topic....");
    }
}

消费者

  • 创建消费者01工程
    在这里插入图片描述

  • 添加依赖(和生产者项目依赖保持一致)

  • 配置整合

rabbitmq.host=192.168.1.104
rabbitmq.port=5672
rabbitmq.username=yemuxia
rabbitmq.password=123456
rabbitmq.virtual-host=/yemuxia_vir

spring-rabbitmq-consumer.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <bean id="springQueueListener" class="com.baiqi.rabbitmq.listener.SpringQueueListener"/>
    <bean id="fanoutListener1" class="com.baiqi.rabbitmq.listener.FanoutListener"/>

<!--    <bean id="topicListenerStar" class="com.baiqi.rabbitmq.listener.TopicListenerStar"/>
    <bean id="topicListenerWell" class="com.baiqi.rabbitmq.listener.TopicListenerWell"/>
    <bean id="topicListenerWell2" class="com.baiqi.rabbitmq.listener.TopicListenerWell2"/>
-->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
       <rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
        <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
        <!--   <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
           <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
           <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
    </rabbit:listener-container>
</beans>
  • 编写消息监听器
package com.baiqi.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class SpringQueueListener implements MessageListener {
    
    
    @Override
    public void onMessage(Message message) {
    
    
        //打印消息
        System.out.println(new String(message.getBody()));
    }
}

package com.baiqi.rabbitmq.listener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class FanoutListener implements MessageListener {
    
    
    @Override
    public void onMessage(Message message) {
    
    
        //打印消息
        System.out.println(new String(message.getBody()));
    }
}

添加此类是为了启动消费者服务

package com.baiqi.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    
    

    public static void main(String[] args) {
    
    
        //初始化IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");


    }
}

测试简单模式

  • 运行消费者,再运行生产者testHelloWorld单元用例
    在这里插入图片描述
    在这里插入图片描述

测试fanout广播模式

  • 创建消费者02项目
    在这里插入图片描述
  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

    <bean id="fanoutListener2" class="com.baiqi.rabbitmq.listener.FanoutListener2"/>
    <!-- <bean id="topicListenerStar" class="com.baiqi.rabbitmq.listener.TopicListenerStar"/>
    <bean id="topicListenerWell" class="com.baiqi.rabbitmq.listener.TopicListenerWell"/>
    <bean id="topicListenerWell2" class="com.baiqi.rabbitmq.listener.TopicListenerWell2"/>
-->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
        <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
        <!--<rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
        <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
        <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>-->
    </rabbit:listener-container>
</beans>
  • 启动消费者01和02项目,并启动生产者testFanout单元测试
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

小结

  • 使用 Spring 整合 RabbitMQ 将组件全部使用配置方式实现,简化编码
  • Spring 提供 RabbitTemplate 简化发送消息 API
  • 使用监听机制简化消费者编码

SpringBoot整合RabbitMQ

生产端

  • 创建生产者SpringBoot工程
    在这里插入图片描述

  • 引入start,依赖坐标

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>

<dependencies>
    <!--2. rabbitmq-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>
  • 编写yml配置,基本信息配置
# 配置RabbitMQ的基本信息  ip 端口 username  password..
spring:
  rabbitmq:
    host: 192.168.1.104 #主机ip
    port: 5672 #端口
    username: yemuxia
    password: 123456
    virtual-host: /yemuxia_vir
  • 定义交换机,队列以及绑定关系的配置类
package com.baiqi.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RabbitMqConfig {
    
    

    //定义交换机的名字
    public static final String  EXCHANGE_NAME = "boot_topic_exchange";
    //定义队列的名字
    public static final String QUEUE_NAME = "boot_queue";

    //1、声明交换机
    @Bean("bootExchange")
    public Exchange bootExchange(){
    
    

        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }



    //2、声明队列
    @Bean("bootQueue")
    public Queue bootQueue(){
    
    

        return QueueBuilder.durable(QUEUE_NAME).build();
    }


    //3、队列与交换机进行绑定
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
    
    
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }

}

  • 注入RabbitTemplate,调用方法,完成消息发送
package com.baiqi.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@SpringBootTest
@RunWith(SpringRunner.class)
public class ProducerTest {
    
    

     //注入 RabbitTemplate
    @Autowired
     private RabbitTemplate  rabbitTemplate;

     @Test
     public void send(){
    
    
         rabbitTemplate.convertAndSend("boot_topic_exchange","boot.haha","boot mq...");
     }
}

消费端

  • 创建消费者SpringBoot工程
    在这里插入图片描述

  • 引入start,依赖坐标(和生产端保持一致)

  • 编写yml配置,基本信息配置(和生产端保持一致)

  • 定义监听类,使用@RabbitListener注解完成队列监听。

package com.baiqi.consumerspringboot;

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


@Component
public class RabbitMQListener {
    
    

      //定义方法进行信息的监听   RabbitListener中的参数用于表示监听的是哪一个队列
      @RabbitListener(queues = "boot_queue")
      public void ListenerQueue(Message message){
    
    
          System.out.println("message:"+new String(message.getBody()));
      }
}


案例测试

运行消费端服务,再运行生产端的单元测试案例
在这里插入图片描述

小结

  • SpringBoot提供了快速整合RabbitMQ的方式
  • 基本信息在yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
  • 生产端直接注入RabbitTemplate完成消息发送
  • 消费端直接使用@RabbitListener完成消息接收

消息的可靠性投递

生产端

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

  • confirm 确认模式
  • return 退回模式

rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer

  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。

我们将利用这两个 callback 控制消息的可靠性投递

案例演示

  • 创建生产端项目,并配置
    在这里插入图片描述
 <!--加载配置文件-->
 <context:property-placeholder location="classpath:rabbitmq.properties"/>

 <!-- 定义rabbitmq connectionFactory -->
 <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                            port="${rabbitmq.port}"
                            username="${rabbitmq.username}"
                            password="${rabbitmq.password}"
                            virtual-host="${rabbitmq.virtual-host}"
                            publisher-confirms="true"
                            publisher-returns="true"
 />
 <!--定义管理交换机、队列-->
 <rabbit:admin connection-factory="connectionFactory"/>

 <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
 <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>




 <!--消息可靠性投递(生产端)-->
 <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"></rabbit:queue>

 <rabbit:direct-exchange name="test_exchange_confirm">
     <rabbit:bindings>
         <rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
     </rabbit:bindings>
 </rabbit:direct-exchange>
package com.baiqi.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author 白起老师
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
    
    

        @Autowired
        private RabbitTemplate  rabbitTemplate;


        //测试   Confirm 模式
    @Test
    public void testConfirm() {
    
    

         //定义回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    
    
            /**
             *
             * @param correlationData 相关配置信息
             * @param ack   exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
    
    
                System.out.println("confirm方法被执行了....");

                 //ack 为  true表示 消息已经到达交换机
                if (ack) {
    
    
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
    
    
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }
            }
        });

           //进行消息发送
        rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","message Confirm...");

        //进行睡眠操作
        try {
    
    
            Thread.sleep(5000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }


    //测试 return模式
    @Test
    public void testReturn() {
    
    

        //设置交换机处理失败消息的模式   为true的时候,消息达到不了 队列时,会将消息重新返回给生产者
        rabbitTemplate.setMandatory(true);

        //定义回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    
    
            /**
             *
             * @param message   消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange  交换机
             * @param routingKey 路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
    
    
                System.out.println("return 执行了....");

                System.out.println("message:"+message);
                System.out.println("replyCode:"+replyCode);
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);

                //处理
            }
        });
        //进行消息发送
        rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","message return...");

        //进行睡眠操作
        try {
    
    
            Thread.sleep(5000);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

  • 创建消费端项目,并配置
    在这里插入图片描述
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>

<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                           port="${rabbitmq.port}"
                           username="${rabbitmq.username}"
                           password="${rabbitmq.password}"
                           virtual-host="${rabbitmq.virtual-host}"/>


<context:component-scan base-package="com.baiqi.listener" />
<!--定义监听器容器  
    -->
<rabbit:listener-container connection-factory="connectionFactory" >
        <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>
</rabbit:listener-container>
package com.baiqi.listener;

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


@Component
public class AckListener implements ChannelAwareMessageListener {
    
    

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    
        //1、获取消息的id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
    
    


        //2、获取消息
        System.out.println("message:"+new String(message.getBody()));

        //3、进行业务处理
        System.out.println("=====进行业务处理====");

        //4、进行消息签收
        channel.basicAck(deliveryTag, true);

        } catch (Exception e) {
    
    

            //拒绝签收
             /*
            第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
             */
            channel.basicNack(deliveryTag, true, true);

        }
    }
}

  • 测试Confirm 模式
    项目运行演示,先运行生产端,再运行消费端(队列和交换机在哪个项目创建,就先启动哪个服务,本案例队列在生产端创建)
    在这里插入图片描述
    在这里插入图片描述
   //进行消息发送    rabbitTemplate.convertAndSend("test_exchange_confirm11","confirm","message Confirm...");

在testConfirm单元用例中故意将交换机名称写错,让其走回调的else分支。
在这里插入图片描述
在这里插入图片描述

  • 测试Return 模式
    将单元测试用例的testReturn中部分逻辑修改如下
//进行消息发送
        rabbitTemplate.convertAndSend("test_exchange_confirm","confirm11","message return...");

此处故意将路由名称写错,让交换机不能正确路由到队列中去
运行该案例
在这里插入图片描述

生产方确认Confirm小结

  • 设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。
  • 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
  • 设置ConnectionFactory的publisher-returns=“true” 开启 退回模式。
  • 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage。

Consumer Ack

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式
有三种确认方式:

  • 自动确认:acknowledge=“none”
  • 手动确认:acknowledge=“manual”
  • 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息

Consumer Ack案例演示

  • 修改原有消费端程序,去掉签收代码,并配置成手动签收
@Component
public class AckListener implements ChannelAwareMessageListener {
    
    

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    
        //1、获取消息的id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
    
    


        //2、获取消息
        System.out.println("message:"+new String(message.getBody()));

        //3、进行业务处理
        System.out.println("=====进行业务处理====");


        } catch (Exception e) {
    
    

        }
    }
}
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
    <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>

</rabbit:listener-container>
  • 生产端程序修改回正常,并运行
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 再次修改消费端程序,模拟异常出现
package com.baiqi.listener;

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


@Component
public class AckListener implements ChannelAwareMessageListener {
    
    

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    
        //1、获取消息的id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
    
    


        //2、获取消息
        System.out.println("message:"+new String(message.getBody()));

        //3、进行业务处理
        System.out.println("=====进行业务处理====");

        //模拟出现异常
        int  i = 5/0;

        //4、进行消息签收
        channel.basicAck(deliveryTag, true);

        } catch (Exception e) {
    
    

            //拒绝签收
             /*
            第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
             */
            channel.basicNack(deliveryTag, true, true);

        }
    }
}

在这里插入图片描述
消费端出现异常之后,拒绝签收并将消息返回至发送端,发送端又开始重发,但是消费端一直再抛异常,导致发送端出现了循环发送消息的情况

Consumer Ack 小结

  • 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
  • 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
  • 如果出现异常,则在catch中调用 basicNack或 basicReject,拒绝消息,让MQ重新发送消息。

消息可靠性总结

  • 持久化
    • exchange要持久化
    • queue要持久化
    • message要持久化
  • 生产方确认Confirm
  • 消费方确认Ack
  • Broker高可用

消费端限流

在这里插入图片描述

消费端限流案例

  • 生产端案例
//批量发送消息,让消费者每次拉去指定的数量
@Test
public void  testQos(){
    
    

    for (int i = 0; i < 10; i++) {
    
    
        // 发送消息
        rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
    }

}
  • 消费端配置修改
<!--定义监听器容器
      acknowledge="manual":手动签收
      prefetch="1":每次抓取多少条消息
      acknowledge="manual" prefetch="2"
    -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="2">

    <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"></rabbit:listener>


</rabbit:listener-container>
package com.baiqi.listener;

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


@Component
public class QosListener implements ChannelAwareMessageListener {
    
    

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
    
    

          //获取到的消息
        System.out.println(new String(message.getBody()));

        Thread.sleep(1000);

        //处理业务逻辑

        //进行消息的签收
        //channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);

    }
}

在不进行签收的情况下,等到消费端签收了这两条内容之后,发送方会继续发送
在这里插入图片描述

消费端限流小结

  • 在< rabbit:listener-container> 中配置 prefetch属性设置消费端一次拉取多少消息
  • 消费端的确认模式一定为手动确认。acknowledge=“manual”

猜你喜欢

转载自blog.csdn.net/yemuxiaweiliang/article/details/121316093