一:确认种类
RabbitMQ的消息确认有两种。
一种是消息发送确认。这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
第二种是消费接收确认。这种是确认消费者是否成功消费了队列中的消息。
二:消息发送确认
(1)ConfirmCallback
通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调。
使用该功能需要开启确认,spring-boot中配置如下:
spring.rabbitmq.publisher-confirms = true
(2)ReturnCallback
通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)
使用该功能需要开启确认,spring-boot中配置如下:
spring.rabbitmq.publisher-returns = true
三:消息接收确认
(1)确认模式
- AcknowledgeMode.NONE:不确认
- AcknowledgeMode.AUTO:自动确认
- AcknowledgeMode.MANUAL:手动确认
spring-boot中配置方法:
spring.rabbitmq.listener.simple.acknowledge-mode = manual
(2)手动确认
未确认的消息数
上图为channel中未被消费者确认的消息数。
通过RabbitMQ的host地址加上默认端口号15672访问管理界面。
(2.1)成功确认
void basicAck(long deliveryTag, boolean multiple) throws IOException;
deliveryTag:该消息的index
multiple:是否批量. true:将一次性ack所有小于deliveryTag的消息。
消费者成功处理后,调用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false)方法对消息进行确认。
(2.2)失败确认
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
throws IOException;
deliveryTag:该消息的index。
multiple:是否批量. true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列。
void basicReject(long deliveryTag, boolean requeue) throws IOException;
deliveryTag:该消息的index。
requeue:被拒绝的是否重新入队列。
channel.basicNack 与 channel.basicReject 的区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。
消息队列的基础知识可以参考:消息队列RabbitMQ基础知识详解
四:思考
(1)手动确认模式,消息手动拒绝中如果requeue为true会重新放入队列,但是如果消费者在处理过程中一直抛出异常,会导致入队-》拒绝-》入队的循环,该怎么处理呢?
第一种方法是根据异常类型来选择是否重新放入队列。
第二种方法是先成功确认,然后通过channel.basicPublish()重新发布这个消息。重新发布的消息网上说会放到队列后面,进而不会影响已经进入队列的消息处理。
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
(2)消息确认的作用是什么?
为了防止消息丢失。消息丢失分为发送丢失和消费者处理丢失,相应的也有两种确认机制。
消息生产者和消费者
import com.rabbitmq.client.Channel;
-
import org.slf4j.Logger;
-
import org.slf4j.LoggerFactory;
-
import org.springframework.amqp.core.*;
-
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
-
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
-
import org.springframework.amqp.rabbit.core.RabbitAdmin;
-
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
-
import org.springframework.amqp.rabbit.support.CorrelationData;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.ResponseBody;
-
import java.io.IOException;
-
import java.util.Date;
-
import java.util.UUID;
-
/**
-
* Created by yangliu on 2018/4/8.
-
*/
-
@Controller
-
@RequestMapping("/rabbitMq")
-
public class TestController {
-
private Logger logger= LoggerFactory.getLogger(TestController.class);
-
@Autowired
-
RabbitAdmin rabbitAdmin;
-
@Bean
-
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
-
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
-
rabbitAdmin.getRabbitTemplate().setConfirmCallback(new MsgSendConfirmCallBack());
-
rabbitAdmin.getRabbitTemplate().setReturnCallback(new MsgSendReturnCallback());
-
return rabbitAdmin;
-
}
-
@RequestMapping("/sendMq")
-
@ResponseBody
-
public String send(String name) throws Exception {
-
String context = "hello "+name+" --" + new Date();
-
String sendStr;
-
for(int i=1;i<=100;i++){
-
sendStr="第["+i+"]个 hello --" + new Date();
-
logger.debug("HelloSender: " + sendStr);
-
sendMessage("myqueue",sendStr);
-
//Thread.sleep(1000);
-
}
-
return context;
-
}
-
/**
-
* 方式一:动态声明exchange和queue它们的绑定关系 rabbitAdmin
-
* @param exchangeName
-
* @param queueName
-
*/
-
protected void declareBinding(String exchangeName, String queueName) {
-
if (rabbitAdmin.getQueueProperties(queueName) == null) {
-
/* queue 队列声明
-
durable=true,交换机持久化,rabbitmq服务重启交换机依然存在,保证不丢失; durable=false,相反
-
auto-delete=true:无消费者时,队列自动删除; auto-delete=false:无消费者时,队列不会自动删除
-
排他性,exclusive=true:首次申明的connection连接下可见; exclusive=false:所有connection连接下*/
-
Queue queue = new Queue(queueName, true, false, false, null);
-
rabbitAdmin.declareQueue(queue);
-
TopicExchange directExchange = new TopicExchange(exchangeName);
-
rabbitAdmin.declareExchange(directExchange);//声明exchange
-
Binding binding = BindingBuilder.bind(queue).to(directExchange).with(queueName); //将queue绑定到exchange
-
rabbitAdmin.declareBinding(binding); //声明绑定关系
-
rabbitAdmin.getRabbitTemplate().setMandatory(true);
-
rabbitAdmin.getRabbitTemplate().setConfirmCallback(new MsgSendConfirmCallBack());//消息确认
-
rabbitAdmin.getRabbitTemplate().setReturnCallback(new MsgSendReturnCallback());//确认后回调
-
} else {
-
rabbitAdmin.getRabbitTemplate().setQueue(queueName);
-
rabbitAdmin.getRabbitTemplate().setExchange(queueName);
-
rabbitAdmin.getRabbitTemplate().setRoutingKey(queueName);
-
}
-
}
-
/**
-
* 发送消息
-
* @param queueName
-
* @param message
-
* @throws Exception
-
*/
-
public void sendMessage(String queueName, String message) throws Exception {
-
declareBinding(queueName, queueName);
-
CorrelationData correlationId = new CorrelationData(UUID.randomUUID().toString());
-
rabbitAdmin.getRabbitTemplate().convertAndSend(queueName, queueName, message,correlationId);
-
logger.debug("[rabbitmq-sendMessage]queueName:{} ,uuid:{},msg:{}",queueName,correlationId.getId(),message);
-
}
-
/**
-
* 消费者
-
* @param connectionFactory
-
* @return
-
*/
-
@Bean
-
public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {
-
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
-
Queue queue = new Queue("myqueue", true, false, false, null);
-
container.setQueues(queue);
-
container.setExposeListenerChannel(true);
-
container.setMaxConcurrentConsumers(1);
-
container.setConcurrentConsumers(1);
-
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认
-
container.setMessageListener(new ChannelAwareMessageListener() {
-
@Override
-
public void onMessage(Message message, Channel channel) throws Exception {
-
byte[] body = message.getBody();
-
try {
-
//业务逻辑
-
logger.info("消费 receive msg : " + new String(body));
-
// 消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
-
//channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
-
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //手动确认确认消息成功消费
-
} catch (Exception e) {
-
logger.info("消费失败: " + new String(body));
-
// ack返回false,并重新回到队列,api里面解释得很清楚
-
try {
-
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
-
} catch (IOException e1) {
-
e1.printStackTrace();
-
}
-
}
-
}
-
});
-
return container;
-
}
-
/*
-
//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
-
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
-
//ack返回false,并重新回到队列,api里面解释得很清楚
-
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
-
//拒绝消息
-
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
-
如果消息没有到exchange,则confirm回调,ack=false
-
如果消息到达exchange,则confirm回调,ack=true
-
exchange到queue成功,则不回调return
-
exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)
-
*/
-
}
失败后return回调:
-
import org.springframework.amqp.core.Message;
-
import org.springframework.amqp.rabbit.core.RabbitTemplate;
-
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
-
@Override
-
public void returnedMessage(Message message, int replyCode, String replyText,
-
String exchange, String routingKey) {
-
System.out.println("确认后回调return--message:"+new String(message.getBody())+",replyCode:"+replyCode+",replyText:"
-
+replyText+",exchange:"+exchange+",routingKey:"+routingKey);
-
}
-
}
确认后回调:
-
import org.springframework.amqp.rabbit.core.RabbitTemplate;
-
import org.springframework.amqp.rabbit.support.CorrelationData;
-
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
-
@Override
-
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
-
if (ack) {
-
System.out.println("消息确认成功cause"+cause);
-
} else {
-
//处理丢失的消息
-
System.out.println("消息确认失败:"+correlationData.getId()+"#cause"+cause);
-
}
-
}