1. 消息确认机制
1.1发送端的确认
是否发送到交换机的确认,是否发送到队列的确认。
自定义RabbitmqConfig-ConnectionFactory的方式:
/**
* 自定义RabbitmqConfig
*/
@Configuration
public class RabbitmqConfig {
@Bean("connectionFactory")
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("ip", 5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("user_admin");
connectionFactory.setPassword("user_admin");
//此设置开启发送到交换机和队列的回调。确保发送端消息ack回调是否执行
connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
return connectionFactory;
}
@Bean("rabbitTemplate")
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
@Bean("directExchange")
public DirectExchange defaultExchange() {
return new DirectExchange("testExchange");
}
@Bean("queue")
public Queue queue() {
return new Queue("jllqueue", true);
}
@Bean("binding")
public Binding binding() {
return BindingBuilder.bind(queue()).to(defaultExchange()).with("testRoutingKey");
}
}
/**
* @Description 确保是否发送到交换机,可以配合配置属性 spring.rabbitmq.publisher-confirm-type 使用。如果配置文件不定义,代码 *中connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED) *rabbitTemplate.setConfirmCallback(confirmCallbackService) 必须定义。
* @Author: jinglonglong
* @Date:2021-6-29
**/
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("### ConfirmCallbackService-ack:"+ack);
if(!ack){
System.out.println("消息发送失败,cause:"+cause);
} else {
System.out.println(" 消息发送成功:"+ JSON.toJSONString(correlationData));
}
}
}
/**
* @Description 确保交换机中消息是否路由到队列,可以配合配置属性:spring.rabbitmq.publisher-returns使用。如果配置文件不定义,*下面的代码中rabbitTemplate.setMandatory(true) rabbitTemplate.setReturnsCallback(returnCallbackService)必须定义。
* @Author: jinglonglong
* @Date:2021-6-29
**/
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnsCallback {
@Override
public void returnedMessage(ReturnedMessage returned) {
System.out.println("### ReturnCallbackService-消息发送失败:"+ JSON.toJSONString(returned));
}
}
/**
* @Description 发送消息
* @Author: jinglonglong
* @Date:2021-6-29
**/
@Component
public class RabbitMqSend {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private ConfirmCallbackService confirmCallbackService;
@Autowired
private ReturnCallbackService returnCallbackService;
public void sendMessage(String msg,String routingKey,String exchange){
//发送消息时设置强制标志; 仅当提供了returnCallback时才适用。
rabbitTemplate.setMandatory(true);
//确保消息是否发送到交换机,成功与失败都会触发
rabbitTemplate.setConfirmCallback(confirmCallbackService);
//确保消息是否发送到队列,成功发送不触发,失败触发
rabbitTemplate.setReturnsCallback(returnCallbackService);
rabbitTemplate.convertAndSend(exchange,routingKey,msg);
}
}
ConfirmCallbackService 和 ReturnCallbackService是回调逻辑,分别是消息发送到交换机和消息发送到队列的回调逻辑类。
如果不自定义RabbitmqConfig-ConnectionFactory,用下面配置替换connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);,可以在配置文件中加配置:
spring.rabbitmq.publisher-confirm-type=CORRELATED
spring.rabbitmq.publisher-returns=true
1.2消费端的确认
消费端确认有三种模式:NONE,MANUAL,AUTO。
NONE:不管消费端消息是否正常消费或者有消费时是否发生异常,都会被确认为消费成功,队列都会删除。
MANUAL:手动确认,是否ack是需要配合消费端的代码 channel.basicAck() 来确认的,如果没有此代码,那么消息一直处于 unacked 状态,一旦消费端重启,或断开连接,那么unacked的消息会重新进入 ready状态,可能会发生重复消费。如果消费成功,通过channel.basicAck确认,队列中会删除该消息。如果消费失败,需要channel.basicNack确保消息重新进入ready状态或者进入死信队列。
AUTO:消费端如果正常消费,则消息自动确认。如果消费异常,并且抛出异常,则会进入unacked 状态,然后重新进入ready状态,重复消费,直到消费成功,有可能发生死循环。如果加了try catch,即使消费者执行异常,则认为消费正常,队列中的消息会自动删除。
以下为实战代码:
@Configuration
public class RabbitmqConfig {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("ip", 5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("user_admin");
connectionFactory.setPassword("user_admin");
return connectionFactory;
}
//定义direct类型的交换机监听容器
@Bean
public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(ConnectionFactory connectionFactory){
DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory = new DirectRabbitListenerContainerFactory();
directRabbitListenerContainerFactory.setConnectionFactory(connectionFactory);
//设置确认模式:NONE,MANUAL,AUTO。当前设置为手动模式
directRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return directRabbitListenerContainerFactory;
}
}
//------以下消费者方式取其一即可,为了方便解释,全列出来-------
/**
* 方式一:不论异常与否,均通过channel.basicAck来确认消息已消费
* 消费业务
*/
@RabbitListener(queues = "jllqueue",containerFactory = "directRabbitListenerContainerFactory")
public void consumer(Message message, Channel channel) throws IOException {
try {
System.out.println("### 执行业务 ### "+message);
} catch (Exception e) {
e.printStackTrace();
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}
//手动确认,设置手动确认后,没有下面代码,那么队列中Unacked状态的数据一直存在,直到消费端服务断开时,这些数据会重新进入到Ready状态中重新消费,这样会导致重复消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
/**
* 方式二:消费正常,通过channel.basicAck确认;消费异常,通过channel.basicReject让消息重返队列或放弃消息
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(queues = "jllqueue",containerFactory = "directRabbitListenerContainerFactory")
public void consumer2(Message message, Channel channel) throws IOException {
System.out.println(message);
try {
int i = 10/0;
} catch (Exception e) {
e.printStackTrace();
System.out.println("记录消费失败的消息");
//第二个参数为true时,让消息重回队列进行消费,容易出现死循环。第二个参数为false,则放弃消息。
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
return;
}
//正常消费,确认已消费消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}