RabbitMQ-高级特性:Consumer_ASK

138 阅读5分钟

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

RabbitMQ-高级特性:Consumer_ASK

ack:它指的是acknowledge(确认),表示消费端收到消息后的确认方式

有三种确认方式

在配置文件中的监听器设置acknowledge="manual"

1 自动确认 acknowledge="none"(默认的方式)

当消费者收到消息到时候,消费者就会自动给broker(中间件 )一个自动挡回执签收,表示消息已经收到了,但是有的情况,消息收到了,但是业务处理出现异常了,这时候消息就相当于丢失,那么这种情况就可以用第二种方式去处理

2 手动确认 acknowledge="manual"

当消费者收到消息到时候,消费者不会立马告诉broker它收到了消息,会等待业务处理没有问题的时候,再去手动调用代码的方式去告诉broker它已经收到了消息,如果业务处理失败抛出异常了,可以做额外的操作,比如说让消息队列broker重新发送

它的一般流程为

1 接收并打印消费者收到(消费)的消息

2 处理业务逻辑

3 如果业务正常执行就进行channel的basicAck手动签收

4 如果业务出现异常就调用basicAck的basicNack拒绝签收,然后让消息重新回到队列,一直发送,知道业务代码正常消费者签收到位置

3 根据异常情况确认 acknowledge="auto"

这种方式比较麻烦,不常使用,需要根据抛出异常的类型不一样去确认做什么样的操作

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



<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
手动签收,业务正常运行

消费者业务执行成功正常签收消费消息

手动签收,业务报错运行

故意让业务代码报错, 消费者业务执行下执行拒绝签收消息,并一直把消息重新放回队列一直进行发送,直到业务代码正常不报错,因为一直发送一直报错,所以就会一直发消息

手动签收,业务报错运行之后又正常运行

这时候把业务错误的代码解决掉,再次发送发现正常签收消息

测试消费者接收消息代码

package com.wyh.listener;



import com.rabbitmq.client.Channel;

import org.springframework.amqp.core.Message;

import org.springframework.amqp.core.MessageListener;

import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;

import org.springframework.stereotype.Component;



import java.io.IOException;

import java.util.concurrent.TimeUnit;



 /**

 *  @program:  SpringBoot_RabbitMQ_Advanced

 *  @description:  RabbitMQ消费者ACK监听器

 *  @author:  魏一鹤

 *  @createDate:  2022-04-06 20:30

 **/





 /**

 * consumer ACK机制

 *  1 如何设置手动签收(默认自动签收) 在配置文件中的监听器设置acknowledge="manual"

 *  2 让监听器类实现ChannelAwareMessageListener接口

 *  3 如果消息成功处理则调用channel的basicAck签收

 *  4 如果消息处理失败则调用channel的basicNack进行拒收,拒收之后broker重新发送consumer,让consumer重新接收这个消息

 *

 **/



 //包扫描注解 把bean加载到spring容器中

@Component

 //实现MessageListener接口并重写onMessage方法

public class RabbitMQACKListener implements ChannelAwareMessageListener {



    @Override

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



        //为了不让业务报错消息重新回到队列刷屏,这里睡一秒方便观看控制台效果

 // TimeUnit.SECONDS.sleep(1);

 //获取消息的签收方

  long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {

            //1 接收并打印消费者收到(消费)的消息

 System.out.println(new String(message.getBody()));

            //2 处理业务逻辑

 System.out.println( "处理业务逻辑......" );

            //故意使得业务报错

 //  int num=3/0;



 //3 如果业务正常执行就进行channel的basicAck手动签收

 //basicAck有两个参数

 //参数1 long deliveryTag 当前收到消息的标签

 //参数2 boolean multiple 允许多条消息被签收

 channel.basicAck(deliveryTag,false);

        } catch (Exception e) {

            //4 如果业务出现异常就调用basicAck的basicNack拒绝签收

 //basicNack有三个参数

 //参数1 long deliveryTag 当前收到消息的标签

 //参数2 boolean multiple 允许多条消息被签收

 //参数3 boolean requeue 重回队列,如果设置为true,则消息重新回到队列里面,broker中间件会重新发送该消息给消费端

 channel.basicNack(deliveryTag,true,true);



            //一个不常用的方法 这个只能单条处理,而basicNack可以多条处理,推荐使用上面的方法

 //channel.basicReject(deliveryTag,true);

 }

    }

}

监听配置

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



 />



    <!--配置监听器bean 扫描这个包的路径 配置类需要加注解-->

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

    <!--定义监听器    acknowledge="" 设置签收方式 -->

 <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" >

        <!--加载对应监听器的类 具体的类名和监听的队列名-->

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

    </rabbit:listener-container>



</beans>

监听者代码测试

package com.wyh.test;



import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;



 /**

 *  @program:  SpringBoot_RabbitMQ_Advanced

 *  @description:  测试rabbitMQACK监听器三种确认模式

 *  @author:  魏一鹤

 *  @createDate:  2022-04-06 20:42

 **/



@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer" )

public class RabbitMQACKListenerTest {



    @Test

    public void testAck(){

    //消费者应该一直开启接收消息 这里写死循环

        while (true) {



        }

    }

}

ConsumerAck小结

1 在rabbit:listener-container标签中设置acknowledge属性,none=自动确认,manual=手动确认,默认为手动确认
2 如果消费端业务没有出现异常,则调用channel.basicAck(deliveryTag,false)方法进行消息签收
3 如果消费端业务出现异常,则在catch中调用basicNack(可以发多条消息)或者basicReject(只能发单条消息,推荐使用前者)方法,拒收消息,让消息返回队列重新发送消息,一直错就会一直发,直到消费端业务代码正常签收到消息
4 推荐使用try catch代码块方式编写此类代码,业务正常写在try中,业务出错catch中重新发送消息