RabbitMQ的高级特性(一)

1、消息的可靠传递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了消息的确认机制,用来控制消息投递的可靠性模式。
消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产这一个应答。

生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障。
在这里插入图片描述

1.1 实现步骤

第一步:在channel上开启确认模式:channel.confirmSelect()

第二步:在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,

根据具体的结果对消息进行重新发送、或者记录日志等后续处理。

rabbitmq 整个消息投递的路径为:

producer—>rabbitmq broker—>exchange—>queue—>consumer

消息从 producer 到 exchange 则会返回一个 confirmCallback 。

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

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

1.1.1 Confirm确认机制-在Spring中开启

①创建项目 rabbitmq-producer-spring
②添加pom文件

<dependencies>
        <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

③ 在resource 文件夹下面添加 配置文件 rabbitmq.properties

rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/

resource 文件夹下面添加 配置文件 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
     确认模式开启:publisher-confirms="true"

    -->
    <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>
</beans>

④创建测试类,添加确认模式

import org.junit.Test;
import org.junit.runner.RunWith;
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.SpringRunner;

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

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 确认模式:
     * 步骤:
     *  1.确认模式开启:在spring-rabbitmq-producer.xml中,使得ConnectionFactory的属性--publisher-confirms=true
     *  2. 在rabbitTemplate定义ConfirmCallBack回调函数
     */

    @Test
    public void testConfirm(){
        //2.定义回调函数
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack){
                    System.out.println("接收消息成功"+ cause);
                }else {
                    System.out.println("接收消息失败" + cause);
                }
            }
        });

        //3. 发送消息
        rabbitTemplate.convertAndSend("test_exchange_confirm000", "confirm", "message confirm....");
    }
}

注意:发送消息时,如果rabbitmq中没有定义名为test_exchange_confirm000的交换机,消息就会发送失败。
此时需要手动打开rabbitmq的管理页面,添加此交换机

第一步
在这里插入图片描述
第二步
在这里插入图片描述
⑤运行程序
在这里插入图片描述

1.1.2 Return 回退机制

Return Listener用于处理一些不可路由的消息

我们的消息生产者,通过指定一个Exchange和Routingkey,把消息送到某一个队列中,

然后我们的消费者监听队列,进行消息处理操作。

但是在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,

这个时候我们需要监听这种不可达的消息,就要使用return listener。

在基础API中有一个关键的配置项:Mandatory:
如果为true,则监听会接收到路由不可达的消息,然后进行后续处理,
如果为false,那么broker端自动删除该消息。(默认false)
在这里插入图片描述

回退机制测试代码

@Test
public void testReturn(){
        /**
         * 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
         * 步骤:
         * 1. 开启回退模式:publisher-returns="true"
         * 2. 设置ReturnCallBack
         * 3. 设置Exchange处理消息的模式:
         * 1. 如果消息没有路由到Queue,则丢弃消息(默认)
         * 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
         */

        //交换机设置强制处理失败消息的模式
        rabbitTemplate.setMandatory(true);


        //设置ReturnCallBack
        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);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);

                //写一些处理代码......
            }
        });

        //3. 发送消息
        rabbitTemplate.convertAndSend("test_exchange_confirm123", "confirm123", "message confirm....");

    }

1.1.3 小结

Confirm确认机制

  • 在xml配置文件中设置 ConnectionFactory的publisher-confirms="true" 开启 确认模式。
  • 使用 rabbitTemplate.setConfirmCallback 设置回调函数。当消息发送到 exchange 后回调 confirm 方法。在方法中判断 ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。

Return回退机制

  • 在xml配置文件中设置 ConnectionFactorypublisher-returns="true" 开启 退回模式。
  • 使用 rabbitTemplate.setReturnCallback 设置退回函数,当消息从exchange 路由到 queue 失败后,如果设置了 rabbitTemplate.setMandatory(true) 参数,则会将消息退回给 producer并执行回调函数returnedMessage

1.2、Consumer Ack(消费端确认)

1.2.1 Consumer Ack 介绍

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

有二种确认方式:

  • 自动确认: acknowledge=“none”
  • 手动确认: acknowledge=“manual”
    在spring中通过xml配置文件来配置
 <!--定义监听器容器
    acknowledge="manual":手动签收
    -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">

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

1.2.2 代码演示

① 创建一个Maven项目 rabbitmq-consumer-spring
②添加pom依赖

<dependencies>
        <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

③在 resource 文件夹下面新建 rabbitmq.properties 文件 和 spring-rabbitmq-consumer.xml 文件

rabbitmq.properties 文件

rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/

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}"/>


    <context:component-scan base-package="com.codez.listener" />

    <!--定义监听器容器
    acknowledge="manual":手动签收
    -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">

        <!--延迟队列效果实现:  一定要监听的是 死信队列!!!-->
        <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>
    </rabbit:listener-container>

</beans>

做好以上的环境搭建之后,采用哪种确认模式,只需要在配置文件中进行修改即可。但是,使用自动确认和手动确认的实现方式不太一样。

自动确认
④添加自动确认监听器,消费端一旦收到消息,就会自动确认收到

@Component
public class AckListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        System.out.println(new String(message.getBody()));

    }
}

手动确认
添加手动确认的监听器,需要确保业务正常处理之后,才会确认收到。

@Component
public class AckListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);

        //1.获取消息传递的标记
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            //①接收消息
            System.out.println(new String(message.getBody()));
            //②处理业务逻辑
            System.out.println("处理业务逻辑");

            //设置一个错误
            int i = 3/0;

            //③手动签收
            /**
             * 第一个参数:表示收到的标签
             * 第二个参数:如果为true表示可以签收所有的消息
             */
            channel.basicAck(deliveryTag,true);
        }catch (Exception e){
            e.printStackTrace();

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

    }
}

⑤创建测试类

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


    @Test
    public void test(){
        while (true){

        }
    }

}

运行测试类,会一直监听消息,查看后台 http://localhost:15672/#/queues

在这里插入图片描述

1.2.3 Consumer Ack 小结

rabbit:listener-container标签中设置acknowledge属性,设置ack方式
none:自动确认,
manual:手动确认

如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,true);方法确认签收消息

如果出现异常,则在catch中调用 basicNack,拒绝消息,让MQ重新发送消息。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值