在rabbitMq中 设置2种消息确认模式
1:自动确认 :RabbitMq种默认的确认模式 自动确认模式下,当 Broker (消息队列服务器实体)成功发送消息给消费者后就会立即把此消息从队列中删除,而不用 等待消费者回送确认消息。
2:手动确认。而在手动确认模式下,当 Broker (消息队列服务器实体) 发送消息给消费者后并不会立即把 此消息删除, 而是要等收到消费者回送的确认消息后才会删除,因此当消费者收到消息已经会发送一个ACK命令指消息确认给发送者,如果消费者因为意外崩溃而没有 发送 ACK 指令, 那么 Broker (消息队列服务器实体) 就会把该消息转发给其他消费者(如果此时没有其他消费者,则 Broker(消息队列服务器实体) 会缓存此消息, 直到有新的消费者注册)。
在RabbitMq 配置手动模式
/**
* 配置消息监听者
* @param connectionFactory
* @return
*/
@Bean
public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//同时监听多个队列
container.setQueues(directRabbitMqQueue());
//设置当前的消费者数量
container.setConcurrentConsumers(1);
//设置当前的消费者数量上限
container.setMaxConcurrentConsumers(5);
//设置是否重回队列
container.setDefaultRequeueRejected(true);
//设置手动签收 --》 这里指定确认方式 Auto为自动 MANUAL为手动
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//设置监听外露
container.setExposeListenerChannel(true);
//设置消息监听
container.setMessageListener(fooMessageListener());
return container;
}
确定手动模式确认后,开始编写消费者和消息回调 ReturnCallback 和 ConfirmCallback
ConfirmCallback:消息发送成功的回调当消息进入Exchange交换器时就进入回调,不管是否进入队列。默认是关闭的需要手动开启在配置ConnectionFactory连接对象时配置
// 确认开启ConfirmCallback回调
connectionFactory.setPublisherReturns(true);
ReturnCallback:当消息进入Exchange交换器时就进入回调,但是未进入队列时回调。这里有二种状态要注意当配置
Mandatory:
为true时,消息通过交换器无法匹配到队列会返回给生产者 并触发MessageReturn
为false时,匹配不到会直接被丢弃
// rabbitMq新版本开启 ReturnCallback
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
配置 ReturnCallback 和 ConfirmCallback
/**
* 消息发送成功的回调
* 需要开启
* # 开启发送确认
* publisher-confirms: true
* @author hw
* @date 2019-07-22 15:44
**/
@Slf4j
@Component
public class ConfirmReturn implements ConfirmCallback {
/**
* 发布者确认的回调。
*
* @param correlationData 回调的相关数据。
* @param ack ack为真,nack为假
* @param cause 一个可选的原因,用于nack,如果可用,否则为空。
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息成功发送到exchange");
} else {
log.error("消息发送exchange失败:" + cause);
}
}
}
实现ConfirmCallback 接口,重写confirm方法即可。
/**
* 发生异常时的消息返回提醒
* 需要开启
* # 开启发送失败退回
* publisher-returns: true
* @author hw
* @date 2019-07-22 15:45
**/
@Slf4j
@Component
public class MessageReturn implements ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("返回消息回调:{} 应答代码:{} 回复文本:{} 交换器:{} 路由键:{}", message, replyCode, replyText, exchange, routingKey);
}
}
ReturnCallback同理
创好 ReturnCallback 和 ConfirmCallback之后还不能直接使用需要将2个实现类配置到 RabbitTemplate对象中
/**
* Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者 并触发MessageReturn
* 为false时,匹配不到会直接被丢弃
*/
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(messageReturn);
rabbitTemplate.setConfirmCallback(confirmReturn);
配置完成之后我们来自定义一个消费者来实现一下 是否和我们配置的相同
/**
* 消费者
* @author hw
*/
@Slf4j
@Service
public class ReceiverMessage {
public ReceiverMessage() {
log.info("ReceiverMessage正在初始化");
}
@RabbitListener(queues = "directRabbitMqQueue",ackMode = "MANUAL")
public void processHandler(String msg, Channel channel,Message message) throws IOException {
try {
log.info("ReceiverMessage 收到消息:{}", msg);
//TODO 具体业务
//告诉服务器收到这条消息 无需再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.info("回调的相关数据:{} ack:{} cause:{} ");
if (message.getMessageProperties().getRedelivered()) {
log.error("消息已重复处理失败,拒绝再次接收...");
// 拒绝消息
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} else {
log.error("消息即将再次返回队列处理...");
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
throw new RuntimeException("自定义");
}
}
}
}
创建一个测试类来跑一下看看
@Test
public void convertAndSend() {
MaMallPojo maMallPojo = new MaMallPojo();
maMallPojo.setId("1");
maMallPojo.setMall("1072484187@qq.com");
maMallPojo.setUserName("hewei");
/**
* 发送消息
*/
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(rabbitmqDirectExchange,rabbitmqDirectRoutingKey,maMallPojo,correlationId);
}
}
rabbitmqDirectExchange和rabbitmqDirectRoutingKey是我自定义的exchange 和 routingKey2者是相互绑定的
: ReceiverMessage 收到消息:{"id":"1","userName":"hewei","mall":"1072484187@qq.com"}
: 消息成功发送到exchange
当exchange 和 routingKey相绑定时,消息通过exchange 和 routingKey进入相对应的队列中则只会触发ConfirmCallback 不会触发ReturnCallback
在次实验一下 exchange 和 routingKey不相互绑定时
: 返回消息回调:(Body:'{"id":"1","userName":"hewei","mall":"1072484187@qq.com"}' MessageProperties [headers={spring_returned_message_correlation=4df9223a-ba36-4226-bb2b-18d4d1da8be8, __TypeId__=com.system.mq.发送邮件.MaMallPojo}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0]) 应答代码:312 回复文本:NO_ROUTE 交换器:directRabbitMqExchange1 路由键:directRabbitMqRoutingKey
: 消息成功发送到exchange
因为 exchange 和 routingKey不相互绑定所以消息无法进入队列,消费者自然也收不到消息。当时exchange是真正存在的所以消息还是会进入 exchange所以还是会触发ConfirmCallback 并且因为无法找到对应的队列所以也会触发ReturnCallback。
自此RabbitMq手动配置模式完成。