RabbitMQ 高级特性 Consumer Ack
- ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
- 三种确认方式:
- acknowledge=“none”
- acknowledge=“manual”
- 根据异常情况确认:acknowledge=“auto”,(使用麻烦,不演示)
- 自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。
- 手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
- 消费者步骤:
-
创建工程
-
pom.xml导包
<dependencies> <!--spring 上下文--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.7.RELEASE</version> </dependency> <!-- spring 整合rabbit--> <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>
-
resources目录下创建rabbitmq.properties文件
rabbitmq.host=192.168.20.146 rabbitmq.port=5672 rabbitmq.username=test rabbitmq.password=test rabbitmq.virtual-host=/test
-
resources目录下创建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" xmlns:rabbitmq="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}" publisher-confirms="true" publisher-returns="true" /> <!-- 定义包扫描--> <context:component-scan base-package="com.yang.listener"/> </beans>
-
创建一个类实现
MessageListener
接口
-
spring-rabbitmq-producer.xml文件配置监听容器(
ref是监听的类,queue-names是监听的队列
RabbitMQ 高级特性 - 消息的可靠性投递中的队列)
<!-- 定义监听器容器 --> <rabbitmq:listener-container connection-factory="connectionFactory"> <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/> </rabbitmq:listener-container>
-
AckListener类的内容
package com.yang.listener; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageListener; import org.springframework.stereotype.Component; @Component public class AckListener implements MessageListener { @Override public void onMessage(Message message) { System.out.println(new String(message.getBody())); } }
-
在测试类写个死循环,看看能不能监听到
package com.yang.test; import org.junit.Test; import org.junit.runner.RunWith; 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; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml") public class ConsumerTest { @Test public void test() { while(true) { } } }
-
运行结果
-
- Ack手动签收(
刚刚其实就是自动签收(默认)
)- 步骤:
-
在spring-rabbitmq-producer.xml文件定义监听容器中配置手动签收
acknowledge="manual"
<!-- 定义监听器容器 --> <rabbitmq:listener-container connection-factory="connectionFactory" acknowledge="manual"> <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/> </rabbitmq:listener-container>
-
-
监听器实现ChannelAwareMessageListener
package com.yang.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; /** * Consumer ACK机制: * 1. 设置手动签收.acknowledge="manual" * 2. 让监听器实现ChannelAwareMessageListener * 3. 如果消息处理成功, 则调用channel的basicAck()签收 * 4. 如果消息处理失败, 则调用channel的basicNack()拒绝签收.broker重新发送给consumer */ @Component public class AckListener implements ChannelAwareMessageListener { @Override public void onMessage(Message message, Channel channel) throws Exception { long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { //1. 接受消息 System.out.println(new String(message.getBody())); //2. 处理业务逻辑 打印一句话代表 System.out.println("处理业务逻辑"); //3. 手动签收 /* basicAck(long deliveryTag, boolean multiple)参数说明: deliveryTag: tag标签 multiple: true允许多条消息被签收 */ channel.basicAck(deliveryTag,true); } catch (Exception e) { //4. 拒绝签收 /* basicNack(long deliveryTag, boolean multiple, boolean requeue)参数说明: deliveryTag: tag标签 multiple: true允许多条消息被签收 requeue: 重回队列.如果为true.则重新回到queue,broker会重新发送消息给消费端 */ channel.basicNack(deliveryTag,true,true); } } }
-
可以假装业务逻辑错误
//2. 处理业务逻辑 打印一句话代表 System.out.println("处理业务逻辑"); int i = 3/0;
- 步骤:
- 小结
- 在rabbit:listener-container标签中设置acknowledge属性,设置ack方式 none:自动确认,manual:手动确认
- 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
- 如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,false);方法确认签收消息
消息可靠性总结
- 持久化
- exchange要持久化
- queue要持久化
- message要持久化
- 生产方确认Confirm
- 消费方确认Ack
- Broker高可用