开启掘金成长之旅!这是我参与「掘金日新计划 · 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) {
}
}
}