RabbitMQ系列6 消息的可靠性传递与ConsumerACK

1.消息可靠性投递

存在的问题

在使用RabbitMQ的时候,我们可能会由于种种原因发送消息丢失或投递失败的场景

就像我们生活中送快递一样,快递员送快递后,需要我们确认签收,才能证明这个快递到我们手上了。如果快递在运输的途中丢了,我们就需要向快递公司反馈,这里面就有一套处理机制。RabbitMQ也是如此

在这里插入图片描述

解决方案

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

1.confirm 确认模式
2.return 退回模式

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

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

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

两种模式介绍

1.publiser-confirm模式可以确保生产者到交换器exchange消息有没有发送成功

使用步骤

1.开启确认模式 publisher-confirms=“true”
2.在rabbitTemplate中定义confirm callBack回调函数

#设置此属性配置可以确保消息成功发送到交换器
spring.rabbitmq.publisher-confirms=true

2.publisher-return模式可以在消息没有被路由到指定的queue时将消息返回,而不是丢弃

#可以确保消息在未被队列接收时返回
spring.rabbitmq.publisher-returns=true

使用步骤

1.开启回退模式:publisher-returns=“true”
2.设置return Callback
3.设置exchange处理消息模式
1. 如果消息没有路由到Queue,则丢弃消息(默认)
2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack

案例演示

演示步骤

1.创建工程
2.添加对应的依赖
3.编写配置文件
4.测试代码

1.创建工程

在这里插入图片描述

2.添加对应的依赖

 <dependencies>
        <!--测试相关-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <!--RabbitMQ相关-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.6.0</version>
        </dependency>
        <!--spring集成rabbitmq-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
        <!--spring相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.2</version>
        </dependency>
    </dependencies>

3.编写配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://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"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               host="${rabbitmq.host}"
                               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_high" name="test_queue_high"/>
    <!--绑定交换机-->
    <rabbit:direct-exchange name="test_exchange_high" >
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_high" key="confirm"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>
</beans>

4.测试代码

public-confirm

/*
    * 确认模式
    * 步骤
    *   1.开启确认模式
    *   2.在rabbitTemplate中定义confirm callBack回调函数
    * */
    @Test
    public void test1() throws InterruptedException {
        /*定义回调*/
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /*
            * 参数介绍从左至右
            * 1.相关配置信息
            * 2.为true则为成功发送到了交换机
            * 3.错误原因
            * */
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String cause) {
                System.out.println("confirm方法被执行了....");
                System.out.println(b);
                if (b) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }

            }
        });
        /*发送消息*/
        /*
        * 参数介绍
        * 1.交换机名称
        * 2.key名称
        * 3.所发送的消息
        * */
        rabbitTemplate.convertAndSend("test_exchange_high","confirm","This is message!!");
        Thread.sleep(2000);
    }

在这里插入图片描述

public-return

 /*
    * 回退模式:当消息发送给Exchange之后,Exchange路由到Queue失败才会执行ReturnCallBack
    *步骤 :
    *   1.开启回退模式:publisher-returns="true"
    *   2.设置return Callback
    *   3.设置exchange处理消息模式
    *     1. 如果消息没有路由到Queue,则丢弃消息(默认)
     *    2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
    * */
    @Test
    public void test2() throws InterruptedException {
        /*当把 Mandatory 参数设置为 true 时,如果交换机无法将消息进行
        路由时,会将该消息返回给生产者,而如果该参数设置为false,如果发现
        消息无法进行路由,则直接丢弃。*/
        /*设置交换机处理消息失败模式*/
        rabbitTemplate.setMandatory(true);
        /*设置returnCallBack*/
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /*
            * 参数介绍,从左到右
            * message:消息对象
            * error code:错误码
            * errorMessage:错误信息
            * exchange:交换机
            * routeKey:路由键
            * */
            @Override
            public void returnedMessage(Message message, int errorCode, String errorMessage, String exchange, String routeKey) {
                System.out.println("returnMessage方法执行了");
                System.out.println("所发送的信息:"+message);
                System.out.println("路由键:"+routeKey);
                System.out.println("错误码:"+errorCode);
            }
        });
        /*发送消息*/
        /*
         * 参数介绍
         * 1.交换机名称
         * 2.key名称
         * 3.所发送的消息
         * */
        rabbitTemplate.convertAndSend("test_exchange_high","confirm2","This is message!!");
        Thread.sleep(200);
    }

小总结

设置ConnectionFactory的publish-confirms=“true”开启确认模式
使用rabbitTemplate.setConfirmCallBack设置回调函数,当消息发送到exchange后回调confirm方法,在方法中判断ack,如果为true,则发送成功,如果为false则发送失败,需要处理
设置ConnectionFactory的publisher-returns=“true” 开启回退模式

使用rabbitTemplate.setReturnCallBack设置退回函数,当消息从exchange路由到queue失败如果设置了rabbitTemplate.setMandatory(true)函数,则会将消息回退给producer,并执行回调函数returnedMessage()

2.Consumer ACK

三种确认方式介绍

ACK指Acknowledge,确认,表示消费端接受到 消息后的确认方式

有三种确认方式:
自动确认:acknowledge="none’
手动确认: acknowledge=“manual”
根据异常情况确认:acknowledge=“auto”(这种方式麻烦,不做讲解)

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

小案例

测试步骤概述

1.创建工程
2.导入相应的依赖坐标
3.编写继承ChannelAwareMessageListener的监听类
4.测试

1.创建工程

在这里插入图片描述

2.导入相应的依赖坐标

<dependencies>
    <!--测试相关-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <!--RabbitMQ相关-->
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.6.0</version>
    </dependency>
    <!--spring集成rabbitmq-->
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    <!--spring相关-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-nop</artifactId>
        <version>1.7.2</version>
    </dependency>
</dependencies>

3.编写继承ChannelAwareMessageListener的监听类

package com.pjh.listen;

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

@Component
public class ACKListenerTwo implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        Thread.sleep(1000);
        try{
            /*接受装换消息*/
            System.out.println(new String(message.getBody()));
            /*处理业务逻辑*/
            System.out.println("处理业务逻辑");
            int i=3/0;
            /*手动签收*/
            channel.basicAck(deliveryTag,true);
        }catch (Exception e){
            /*拒绝签收*/
            /*
            * 第三个参数 requeue:重回队列 如果设置为true则重回消费端
            * broker会重新发送该消息给消费端
            * */
            System.out.println("出错了!!");
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

手动编写一个错误
在这里插入图片描述

4.测试

package com.pjh;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbit-consumer.xml")
public class test {
    @Test
    public void test(){
        while(true){

        }
    }
}

测试结果

在这里插入图片描述

主要方法及其参数介绍:

1. channel.basicAck()

参数
deliveryTag:该消息的index
multiple:是否批量处理.true:将一次性ack所有小于deliveryTag的消息

void basicAck(long deliveryTag, boolean multiple) throws IOException;

2. channel.basicNack()

参数
deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息
requeue:被拒绝的是否重新入队列 注意:如果设置为true ,则会添加在队列的末端

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

小结

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

如果在消费端没有出现异常,则调用channel.basic(delivery Tag,false)方法确认签收消息

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

猜你喜欢

转载自blog.csdn.net/pjh88/article/details/114060235