RabbitMQ消息可靠投递-confirm和return

115 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情

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

RabbitMQ整个消息投递的流程路径为

producer(生产者)->rabbitmq broker(中间件)->exchange(交互机)->queue(队列)->consumer(消费者)

1 confirm 确认模式

消息从producer到exchange则返回一个confirmCallBack(回调函数),不管消息是否成功发送,这个回调函数都会执行,只不过返回值不同(true或者false)

pom



<dependencies>

    <!--spring上下文-->

 <dependency>

        <groupId>org.springframework</groupId>

        <artifactId>spring-context</artifactId>

        <version>5.1.7.RELEASE</version>

    </dependency>

    <!--spring整合amqp-->

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

配置文件

 #ip 172.16.98.133

rabbitmq.host=localhost

  #端口

rabbitmq.port=5672

  #用户名

rabbitmq.username=weiyihe

  #密码

rabbitmq.password=weiyihe

  #虚拟机

rabbitmq.virtual-host= /itcast_wyh
 <? 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:application.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"



 />



    <!--定义管理交换机,队列-->

 <rabbit:admin connection-factory="connectionFactory" />



    <!--定义rabbitTemplate对象操作可以在代码中调用api发送消息-->

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

        <!--绑定queue-->

 <rabbit:bindings>

            <rabbit:binding queue="test_queue_confirm" key="confirm" ></rabbit:binding>

        </rabbit:bindings>

    </rabbit:direct-exchange>



</beans>

测试代码



package com.producer.test;



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.SpringJUnit4ClassRunner;



 /**

 *  @program:  SpringBoot_RabbitMQ_Advanced

 *  @description:测试确认模式消息是否发送成功

  *  @author:  魏一鹤

 *  @createDate:  2022-04-04 23:10

 **/



 //spring配置文件

@RunWith(SpringJUnit4ClassRunner.class)

 //加载文件的路径

@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml" )

public class ProducerTest {

    //注入 RabbitTemplate

 @Autowired

    private RabbitTemplate rabbitTemplate;



    /**

 * 确认模式:

 * 步骤

 * 1确认模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-confirms="true"

 * 2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)

 **/

 @Test

    public void testConfirm() {

        //定义确认模式的回调函数

  rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            @Override

            //匿名内部类

 /**

 * confirm有三个参数 下面一一说明

 * CorrelationData correlationData 相关的配置信息

 * boolean ack  exchange交换机是否成功收到了消息 true成功false失败

 * String cause 失败原因 ack=false

 **/

  public void confirm(CorrelationData correlationData, boolean ack, String cause) {

                System.out.println( "确认模式的回调函数被执行了!" ); //确认模式的回调函数被执行了!

 System.out.println( "消息是否发送成功?" +ack);

                if(ack){

                    //交换机接收成功生产者发送的消息

 System.out.println( "接收成功消息!原因是:" +cause);

                }else{

                    //交换机接收没有成功生产者发送的消息

 System.out.println( "接收失败消息!原因是:" +cause);

                    //将来会做处理,就算消息发送失败也会重新去发送消息,保证消息第二次发送成功

                }

            }

        });

        //发送消息

  rabbitTemplate.convertAndSend( "test_exchange_confirm" , "confirm" , "message confirm..." );

    }

}
1确认模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-confirms="true"

2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)



package com.producer.test;



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.SpringJUnit4ClassRunner;



 /**

 *  @program:  SpringBoot_RabbitMQ_Advanced

 *  @description: 

  *  @author:  魏一鹤

 *  @createDate:  2022-04-04 23:10

 **/



 //spring配置文件

@RunWith(SpringJUnit4ClassRunner.class)

 //加载文件的路径

@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml" )

public class ProducerTest {

    //注入 RabbitTemplate

 @Autowired

    private RabbitTemplate rabbitTemplate;



    /**

 * 确认模式:

 * 步骤

 * 1确认模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-confirms="true"

 * 2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)

 **/

 @Test

    public void testConfirm() {

        //定义确认模式的回调函数

  rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            @Override

            //匿名内部类

 /**

 * confirm有三个参数 下面一一说明

 * CorrelationData correlationData 相关的配置信息

 * boolean ack  exchange交换机是否成功收到了消息 true成功false失败

 * String cause 失败原因 ack=false

 **/

  public void confirm(CorrelationData correlationData, boolean ack, String cause) {

                System.out.println( "确认模式的回调函数被执行了!" ); //确认模式的回调函数被执行了!

 System.out.println( "消息是否发送成功?" +ack);

                if(ack){

                    //交换机接收成功生产者发送的消息

 System.out.println( "接收成功消息!原因是:" +cause);

                }else{

                    //交换机接收没有成功生产者发送的消息

 System.out.println( "接收失败消息!原因是:" +cause);

                    //将来会做处理,就算消息发送失败也会重新去发送消息

                }

            }

        });

        //发送消息

  rabbitTemplate.convertAndSend( "test_exchange_confirm" , "confirm" , "message confirm..." );

    }

}

测试成功发送消息

确认模式的回调函数被执行了!

消息是否发送成功?true

接收成功消息!原因是:null

测试失败发送消息

错误修改交换机的名称,找不到交换机名称就会报错,验证发送消息失败

确认模式的回调函数被执行了!

消息是否发送成功?false

接收失败消息!原因是:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'test_exchange_confirm1111' in vhost '/itcast_wyh', class-id=60, method-id=40)

2 return退回模式

消息从exchange->queue投递失败则会返回一个returnCallBack(回调函数)

我们可以利用以上两个callBack(回调函数)控制消息的可靠性传递(是否发送消息成功)

1回退模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-returns="true"

2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)


package com.producer.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;



 /**

 *  @prog ram: SpringBoot_RabbitMQ_Advanced

 *  @description:  测试确认模式消息是否发送成功

 *  @author:  魏一鹤

 *  @createDate:  2022-04-04 23:10

 **/



 //spring配置文件

@RunWith(SpringJUnit4ClassRunner.class)

 //加载文件的路径

@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml" )

public class ProducerTest {

    //注入 RabbitTemplate

 @Autowired

    private RabbitTemplate rabbitTemplate;





    /**

 * 回退模式:当消息发送给Exchange交换机后,交换机路由到queue失败时才会执行ReturnCallBack

 * 步骤

 * 1回退模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-returns="true"

 * 2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)

 * 3设置Exchange处理消息的模式 它有两种模式

 *   3.1 第一种模式:如果消息没有路由到queue队列,则会丢弃消息(默认的方式)

 *   3.2 第二种模式:如果消息没有路由到queue队列,则返回给消息的发送方ReturnCallBack

 **/



 @Test

    public void testReturn() {



        //由于处理消息的模式默认是如果消息没有路由到queue队列,则会丢弃消息

 //所以需要设置交换机处理消息的模式,交换机会把消息返回给对应的生产者,生产者通过监听就能拿到消息

  rabbitTemplate.setMandatory(true);

        //编写returnCallBack回调函数

  rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){

            @Override

            public void returnedMessage(Message message, int i, String s, String s1, String s2) {

                System.out.println( "返回模式的回调函数被执行了!" );

            }



        });



        //发送消息

  rabbitTemplate.convertAndSend( "test_exchange_confirm" , "confirm111" , "message confirm..." );

    }

}
3设置Exchange处理消息的模式 它有两种模式

第一种模式:如果消息没有路由到queue队列,则会丢弃消息(默认的方式)

测试发送消息失败第一种模式,丢弃消息

第二种模式:如果消息没有路由到queue队列,则返回给消息的发送方ReturnCallBack

 <? 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:application.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对象操作可以在代码中调用api发送消息-->

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

        <!--绑定queue-->

 <rabbit:bindings>

            <rabbit:binding queue="test_queue_confirm" key="confirm" ></rabbit:binding>

        </rabbit:bindings>

    </rabbit:direct-exchange>



</beans>
 //由于处理消息的模式默认是如果消息没有路由到queue队列,则会丢弃消息

 //所以需要设置交换机处理消息的模式,交换机会把消息返回给对应的生产者,生产者通过监听就能拿到消息

rabbitTemplate.setMandatory(true);

测试发送消息失败第二种模式,调用回调函数告知信息



package com.producer.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;



 /**

 *  @prog ram: SpringBoot_RabbitMQ_Advanced

 *  @description:  测试确认模式消息是否发送成功

 *  @author:  魏一鹤

 *  @createDate:  2022-04-04 23:10

 **/



 //spring配置文件

@RunWith(SpringJUnit4ClassRunner.class)

 //加载文件的路径

@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml" )

public class ProducerTest {

    //注入 RabbitTemplate

 @Autowired

    private RabbitTemplate rabbitTemplate;





    /**

 * 回退模式:当消息发送给Exchange交换机后,交换机路由到queue失败时才会执行ReturnCallBack

 * 步骤

 * 1回退模式的开启:在connectionFactory中开启,默认是false不开启的 publisher-returns="true"

 * 2回调函数的编写:在RabbitTemplate模板工具类定义ConfirmCallBack(回调函数).当消息发送出去的时候回调函数会自动执行,返回true(成功)或者false(失败)

 * 3设置Exchange处理消息的模式 它有两种模式

 *   3.1 第一种模式:如果消息没有路由到queue队列,则会丢弃消息(默认的方式)

 *   3.2 第二种模式:如果消息没有路由到queue队列,则返回给消息的发送方ReturnCallBack

 **/



 @Test

    public void testReturn() {



        //由于处理消息的模式默认是如果消息没有路由到queue队列,则会丢弃消息

 //所以需要设置交换机处理消息的模式,交换机会把消息返回给对应的生产者,生产者通过监听就能拿到消息

  rabbitTemplate.setMandatory(true);

        //编写returnCallBack回调函数

  rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){

            @Override

            //匿名内部类

 /**

 * returnedMessage有五个参数 下面一一说明

 * Message message 消息对象

 * int replyCode    返回编码 错误码

 * String replyText 错误信息

 * String exchange  交换机

 * String routingKey 路由键

 **/

  public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {

                System.out.println( "返回模式的回调函数被执行了!" );

                System.out.println( "消息对象:" +message);

                System.out.println( "返回编码(错误码):" +replyCode);

                System.out.println( "错误信息:" +replyText);

                System.out.println( "交换机:" +exchange);

                System.out.println( "路由键:" +routingKey);

                //将来会做处理 把信息重新路由



 //-----------------------------------------打印信息

 //返回模式的回调函数被执行了!

 //消息对象:(Body:'message confirm...' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])

 //返回编码(错误码):312

 //错误信息:NO_ROUTE

 //交换机:test_exchange_confirm

 //路由键:confirm111

 }



        });

        //发送消息

  rabbitTemplate.convertAndSend( "test_exchange_confirm" , "confirm" , "message confirm..." );

    }

}

返回模式的回调函数被执行了!

消息对象:(Body:'message confirm...' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])

返回编码(错误码):312

错误信息:NO_ROUTE

交换机:test_exchange_confirm

路由键:confirm111

3 两种消息可靠性传递总结
1 confirm确认模式

1 在connectionFactory中设置开启确认模式.默认是false不开启的 publisher-confirms="true"

2 使用rabbitTemplate.setConfirmCallBack()设置回调函数,当消息发送到exchange交换机后回调confirm方法,在方法中判断ack,如果为true,发送成功,false发送失败,需要进行处理

2 return退回模式

1 在connectionFactory中设置开启回退模式.默认是false不开启的 publisher-returns="true"

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

3 在rabbitMQ中也提供了事务机制,但是性能比较差,不推荐使用

使用channel下列方法,完成事务控制

txSelect() 用于将当前channel设置成transaction模式

txCommon() 用于提交事务

toRollback() 用于回滚事务