这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
RabbitMQ消费者消息确认的三种机制:
- 自动确认(AcknowledgeMode.NONE)
- RabbitMQ消费者默认为自动确认,不会管消费者是否成功消费/处理了消息
- 根据情况确认(AcknowledgeMode.AUTO)
- 如果消息成功被消费(成功的意思是在消费的过程中没有抛出异常),则自动确认
- 当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(不重新入队列)
- 当抛出 ImmediateAcknowledgeAmqpException 异常,则消费者会被确认
- 其他的异常,则消息会被拒绝,且 requeue = true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过 setDefaultRequeueRejected(默认是true)去设置
- 手动确认(AcknowledgeMode.MANUAL)
- 消费者收到消息后,手动对消息进行处理,完成消费
- Basic.Ack :用于确认当前消息
- Basic.Nack :用于否定当前消息
- Basic.Reject :用于拒绝当前消息
消息接收配置类
记得先把之前的监听类都注释掉
package com.chentawen.rabbitmqconsumer.config;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* 消息接收配置类
* @author admin
*/
@Configuration
public class MessageListenerConfig {
/**
* RabbitTemplate 连接工厂
*/
@Resource
private CachingConnectionFactory connectionFactory;
/**
* 消息接收处理类
*/
@Resource
private MyAckReceiver myAckReceiver;
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
// 消费者数量,默认10
container.setConcurrentConsumers(1);
// 每个消费者获取最大投递数量 默认50
container.setMaxConcurrentConsumers(1);
// 自动确认消息
// container.setAcknowledgeMode(AcknowledgeMode.NONE);
// 根据情况确认消息
// container.setAcknowledgeMode(AcknowledgeMode.AUTO);
// 手动确认消息
// container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//设置一个队列
container.setQueueNames("MyDirectQueue");
//如果同时设置多个如下: 前提是队列都是必须已经创建存在的
// container.setQueueNames("MyDirectQueue","MyDirectQueue2","MyDirectQueue3");
//另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
//container.setQueues(new Queue("MyDirectQueue",true));
//container.addQueues(new Queue("MyDirectQueue2",true));
//container.addQueues(new Queue("MyDirectQueue3",true));
container.setMessageListener(myAckReceiver);
return container;
}
}
复制代码
消息接收处理类
package com.chentawen.rabbitmqconsumer.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 消息接收处理类
* @author admin
*/
@Component
class MyAckReceiver implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
// 唯一标识 ID
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
String msg = message.toString();
// System.out.println("msg: " + msg);
// System.out.println("------------------------------");
Map<String, String> msgMap = stringToMap(msg);
String messageId = msgMap.get("messageId");
String messageData = msgMap.get("messageData");
String createTime = msgMap.get("createTime");
System.out.println("MyAckReceiver messageId: " + messageId + " messageData: " + messageData + " createTime: " + createTime);
System.out.println("------------------------------");
/**
* 确认消息,参数说明:
* long deliveryTag:唯一标识 ID
* boolean multiple:是否批处理,当该参数为 true 时,则可以一次性确认 deliveryTag 小于等于传入值的所有消息
*/
// channel.basicAck(deliveryTag, true);
/**
* 否定消息,参数说明:
* long deliveryTag:唯一标识 ID
* boolean multiple:是否批处理,当该参数为 true 时,则可以一次性确认 deliveryTag 小于等于传入值的所有消息
* boolean requeue:如果 requeue 参数设置为 true 时,则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者
* 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,而不会把它发送给新的消费者
*/
//channel.basicNack(deliveryTag, true, false);
} catch (Exception e) {
e.printStackTrace();
/**
* 拒绝消息,参数说明:
* long deliveryTag:唯一标识 ID
* boolean requeue:如果 requeue 参数设置为 true 时,则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者
* 如果 requeue 参数设置为 false 时,则 RabbitMQ 立即会还把消息从队列中移除,而不会把它发送给新的消费者
*/
// channel.basicReject(deliveryTag, true);
}
}
/**
* 这是未经处理的消息内容:
* (Body:'{createTime=2021年08月17日 21:53:42, messageId=753047b6-3435-44e9-8382-318ea7913944, messageData=Hello World!}'
* MessageProperties [headers={spring_listener_return_correlation=b14787e0-6920-4135-9a42-044f0c4cb638},
* contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT,
* priority=0, redelivered=false, receivedExchange=MyDirectExchange, receivedRoutingKey=DirectRoutingKey,
* deliveryTag=3, consumerTag=amq.ctag-Lh7_hz1siXgqXFh8bMxLBw, consumerQueue=MyDirectQueue])
* 将接收到的消息转换为map格式
* @param str
* @return
*/
private static Map<String, String> stringToMap(String str) {
Map<String, String> map = new HashMap<>(16);
String[] split = str.split("'");
String data = split[1];
String[] split2 = data.substring(1, data.length() - 1).split(",");
for (String s : split2) {
String[] strings = s.split("=");
map.put(strings[0].trim(), strings[1].trim());
}
return map;
}
}
复制代码
先注释掉 消息接收配置类 确认消息方式以及 消息接收配置类 手动处理的代码(上面的代码已注释),启动生产者和消费者的项目,发送一条消息查看控制台的打印结果(上面的代码监听的是MyDirectQueue)
可以发现消费者默认是自动确认消息的
根据情况确认消息的触发条件上面已经详细说明了,因为用的比较少就不演示了,项目中用的较多的是手动确认的方式
在 消息接收配置类 中修改确认消息方式为手动确认,消息接收配置类中 打开手动确认消息的代码,查看控制台打印信息,大家自行进行测试
这里给大家看下测试发生异常,消息重新存入队列发送给下一个消费者
- 首先直接在消息接收处理类中加上异常(throw new Exception("测试异常"))
- 捕捉异常后手动拒绝消息(channel.basicReject(deliveryTag, true))
- 创建一个监听类(监听MyDirectQueue)
@Component
@RabbitListener(queues = "MyDirectQueue")
public class DirectReceiver {
@RabbitHandler
public void process(Map MessageData) {
System.out.println("rabbitmq-consumer1接收到消息 : " + MessageData.toString());
}
}
复制代码
- 重启项目发送消息,查看控制台
可以发现抛出了异常后,消息重新进入了队列,刚刚创建的消息者进行了消费
消费来自不同队列的消息进行业务处理
记得注释刚刚及以前的消费者,以免被抢先消费了
- 在消息配置类中添加监听队列
//如果同时设置多个如下: 前提是队列都是必须已经创建存在的
container.setQueueNames("MyDirectQueue", "MyTopicQueueA","MyTopicQueueB");
复制代码
- 在消息接受处理类添加业务处理代码
if ("MyDirectQueue".equals(message.getMessageProperties().getConsumerQueue())) {
System.out.println("消费的消息来自的队列:" + message.getMessageProperties().getConsumerQueue());
System.out.println("消费来自MyDirectQueue的消息,进行对应业务处理...");
System.out.println("------------------------------");
}
if ("MyTopicQueueA".equals(message.getMessageProperties().getConsumerQueue())) {
System.out.println("消费的消息来自的队列:" + message.getMessageProperties().getConsumerQueue());
System.out.println("消费来自MyTopicExchange.A的消息,进行对应业务处理...");
System.out.println("------------------------------");
}
if ("MyTopicQueueB".equals(message.getMessageProperties().getConsumerQueue())) {
System.out.println("消费的消息来自的队列:" + message.getMessageProperties().getConsumerQueue());
System.out.println("消费来自MyTopicExchange.B的消息,进行对应业务处理...");
System.out.println("------------------------------");
}
复制代码
- 重启项目,分别发送消息给两个队列,查看控制台
可以对来自不同队列的消息进行相应的业务处理
以上就是本期内容,后续内容持续更新