SpringBoot整合RabbitMq生产者和消费者消息确认机制(ack)
首先引入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
然后在配置文件application.yml添加mq相关配置
spring:
application:
name: mq-test
rabbitmq:
host: 192.168.1.180
port: 5672
# 发送回调
publisher-returns: true
# 发送确认
publisher-confirm-type: correlated
listener:
simple:
#手动确认
acknowledge-mode: manual
template:
#当mandatory标志位设置为true时,如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息,
#那么broker会调用basic.return方法将消息返还给生产者;当mandatory设置为false时,出现上述情况broker会直接将消息丢弃;
#通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者
mandatory: true
消息发送确认
生产者发送消息,是先发送消息到Exchange,然后Exchange再路由到Queue。这中间就需要确认两个事情,第一,消息是否成功发送到Exchange;第二,消息是否正确的通过Exchange路由到Queue。
spring提供了两个回调函数来处理这两种消息发送确认。
ConfirmCallback和ReturnCallback
-
实现ConfirmCallback并重写confirm(CorrelationData correlationData, boolean ack, String cause)回调方法,可以确认消息是否发送到Exchange。
-
实现ReturnCallback并重写returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey)回调方法,可以确认消息从EXchange路由到Queue失败。注意:这里的回调是一个失败回调,只有消息从Exchange路由到Queue失败才会回调这个方法。
生效的话需要在yml文件配置
publisher-returns: true
publisher-confirm-type: correlated
回调配置类
package com.koko.mqdemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Description 消息发送确认
* <p>
* ConfirmCallback 只确认消息是否正确到达 Exchange 中
* ReturnCallback 消息没有正确到达队列时触发回调,如果正确到达队列不执行
* <p>
* 1. 如果消息没有到exchange,则confirm回调,ack=false
* 2. 如果消息到达exchange,则confirm回调,ack=true
* 3. exchange到queue成功,则不回调return
* 4. exchange到queue失败,则回调return
*/
@Component
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private final Logger log= LoggerFactory.getLogger(this.getClass());
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(this); //指定 ConfirmCallback
rabbitTemplate.setReturnCallback(this); //指定 ReturnCallback
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息发送成功:,{}", correlationData);
} else {
log.info("消息发送失败:,{}", cause);
}
}
/**
* 回调
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("消息主体:,{}", message);
log.info("应答码:,{}", replyCode);
log.info("描述:,{}", replyText);
log.info("消息使用的交换器 exchange :,{}", exchange);
log.info("消息使用的路由键 routing :,{}", routingKey);
}
}
mq配置类
package com.koko.mqdemo;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 定义队列名和交换机
*/
@Configuration
public class DirectMqConfig {
/**
* 交换机名称
*/
public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
/**
* 绑定key,交换机绑定队列时需要指定
*/
public static final String BINGDING_KEY_TEST1 = "direct_key1";
public static final String BINGDING_KEY_TEST2 = "direct_key2";
/**
* 队列名称
*/
public static final String QUEUE_TEST1 = "test1";
public static final String QUEUE_TEST2 = "test2";
/**
* 构建DirectExchange交换机
*
* @return
*/
@Bean
public DirectExchange directExchange() {
// 支持持久化,长期不用补删除
return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
}
/**
* 构建序列
*
* @return
*/
@Bean
public Queue test1Queue() {
// 支持持久化
return new Queue(QUEUE_TEST1, true);
}
@Bean
public Queue test2Queue() {
// 支持持久化
return new Queue(QUEUE_TEST2, true);
}
/**
* 绑定交交换机和
*
* @return
*/
@Bean
public Binding test1Binding() {
return BindingBuilder.bind(test1Queue()).to(directExchange()).with(BINGDING_KEY_TEST1);
}
@Bean
public Binding test2Binding() {
return BindingBuilder.bind(test2Queue()).to(directExchange()).with(BINGDING_KEY_TEST2);
}
/**
* 实例化操作模板
*
* @param connectionFactory
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//必须为true,否则无法触发returnedMessage回调,消息丢失
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
}
生产者下发类
package com.koko.mqdemo;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonParser;
import com.rabbitmq.tools.json.JSONUtil;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.GsonBuilderUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
public class SendController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/send")
public void send(){
CorrelationData data = new CorrelationData(UUID.randomUUID().toString());
Map<String,String> map=new HashMap<>();
map.put("key","hello");
try {
Message message= MessageBuilder.withBody(JSON.toJSONString(map).getBytes("UTF-8")).setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN).build();
rabbitTemplate.convertAndSend(DirectMqConfig.DIRECT_EXCHANGE_NAME,DirectMqConfig.BINGDING_KEY_TEST1,message,data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
@GetMapping("/sendError")
public void sendError(){
CorrelationData data = new CorrelationData(UUID.randomUUID().toString());
Map<String,String> map=new HashMap<>();
map.put("key","hello");
try {
Message message= MessageBuilder.withBody(JSON.toJSONString(map).getBytes("UTF-8")).setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN).build();
rabbitTemplate.convertAndSend(DirectMqConfig.DIRECT_EXCHANGE_NAME,"test",message,data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
启动之后mq自动创建exchange和queue
消费者端
package com.koko.mqdemo;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class ConsumerListener {
private final Logger log= LoggerFactory.getLogger(this.getClass());
@RabbitListener(queues=DirectMqConfig.QUEUE_TEST1)
public void getDirectMessage(Channel channel, Message message) throws IOException {
log.info("【mq接收到的消息为:】,{}",new String(message.getBody()));
try {
// 模拟执行任务
Thread.sleep(1000);
// 模拟异常
String is = null;
//is.toString();
// 确认收到消息,false只确认当前consumer一个消息收到,true确认所有consumer获得的消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
log.info("消息已重复处理失败,拒绝再次接收");
// 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} else {
log.info("消息即将再次返回队列处理");
// requeue为是否重新回到队列,true重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
//e.printStackTrace();
}
}
}
正常请求send接口日志打印如下
然后我们请求sendError接口,sendError接口的路由是故意写错的,我们看下有没有进入return回调方法
日志打印的很清楚exchange没有找到queue所以成功回调return方法。
消费者消息手动确认
SpringBoot集成RabbitMQ确认机制分为三种:none 、auto、manual
none意味着没有任何的应答会被发送。
manual意味着监听者必须通过调用Channel.basicAck()来告知所有的消息。
auto意味着容器会自动应答,除非MessageListener抛出异常,这是默认配置方式。
正常接收到消息后日志打印如下
当我们接收到消息处理时故意抛出异常
// 模拟异常
String is = null;
is.toString();
然后再看控制台日志
在处理业务时如果有异常mq回再次向消费者推送一遍消息
参考文章