在SpringBoot 整合 Rabbitmq时,我们开启消息发布者手动确认模式配置如下:
# 消息中间件
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
# 消息生产者确认模式
publisher-confirm-type: correlated
# 消息生产者回调开启
publisher-returns: true
以上配置开启后我们需要在生产者端手动编写回调方式如下:
@Slf4j
@SpringBootTest
public class DHomeMessageApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMessage(){
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(2000);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
MessageDTO messageDTO = new MessageDTO();
messageDTO.setTitle("hello rabbitmq 我是帅哥!!");
messageDTO.setContext(format.format(new Date()));
CorrelationData correlationData = new CorrelationData();
String messageStr = JSONObject.toJSONString(messageDTO);
ReturnedMessage returnedMessage = new ReturnedMessage(new Message(messageStr.getBytes()), 1, "1", MqConstants.EXCHANGE_NAME, "");
correlationData.setReturned(returnedMessage);
// MqConstants.EXCHANGE_NAME 交换机名称
// MqConstants.QUEUE_NAME 队列名称
rabbitTemplate.convertAndSend(MqConstants.EXCHANGE_NAME,MqConstants.QUEUE_NAME,messageDTO,correlationData);
// 监听消息是否发送到交换机 回调
rabbitTemplate.setConfirmCallback(((correlationData1, b, s) -> {
if(b){
log.error("发送消息成功");
}else {
log.error("发送消息到交换机失败");
ReturnedMessage returned = correlationData.getReturned();
Message message = returned.getMessage();
byte[] body = message.getBody();
String s1 = new String(body);
log.info("ConfirmCallback: " + "相关数据:" + s1 );
log.info("ConfirmCallback: " + "确认是否到达交换机:" + b);
log.info("ConfirmCallback: " + "原因:" + s);
}
}));
// 消息发送到交换机成功,但是发送到队列失败 回调
rabbitTemplate.setReturnsCallback(returnedMessage1 -> {
String s = returnedMessage1.getMessage().getBody().toString();
log.error("发送消息到队列失败");
log.error("消息内容:{}",s);
});
}catch (Exception e){
log.error("发送消息失败");
e.printStackTrace();
}
}
}
}
以上代码执行时候会爆出错误:
以上报错原因是 rabbitMqTemplate 默认是单例的,一个rabbitMqTemplate 只能监听一个 ConfirCallback。网上的解决办法是:
修改 rabbitMqTemplate 为多例模式:如下: 我们定义一个多例模式的 rabbitMqTemplate 并在使用时注入自定义的多例Bean
@Bean(name = "rabbitTemplatePrototype")
//设置为多实例的,每次注入都使用不同的bean
@Scope("prototype")
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
return rabbitTemplate;
}
在生产者使用:
@Resource(name = "rabbitTemplatePrototype")
private RabbitTemplate rabbitTemplate;
再次运行: 使用了我们自定义的 多例模板之后,运行测试代码还是会报错如下:
这时候网上的文章又说 在Controller 中调用 并且指定这个Controller 也为多例的 ,我们做出修改:
以下是执行结果:
依旧是报错了,。。。。。。
暂时不管 报错,但是如果把消息发送的代码写在controller 中未免显得太过low (违反三层设计)。
一下提供可行的方案:
直接在定义rabbitMqTemplate 模板的Bean 中,设置回调方法如下:
@Bean(name = "rabbitTemplate")
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 设置消息从生产者发送至 rabbitmq broker 成功的回调 (保证信息到达 broker)
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
// ack=true:消息成功发送到Exchange
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
ReturnedMessage returned = correlationData.getReturned();
Message message = returned.getMessage();
byte[] body = message.getBody();
String s = new String(body);
boolean valid = JSONObject.isValid(s);
MessageDTO messageDTO=null;
if(valid){
messageDTO = JSONObject.parseObject(s, MessageDTO.class);
}
log.info("ConfirmCallback: " + "相关数据:" + messageDTO );
log.info("ConfirmCallback: " + "确认是否到达交换机:" + ack);
log.info("ConfirmCallback: " + "原因:" + cause);
}
});
// 设置信息从交换机发送至 queue 失败的回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("ReturnCallback: " + "消息:" + message);
log.info("ReturnCallback: " + "回应码:" + replyCode);
log.info("ReturnCallback: " + "回应信息:" + replyText);
log.info("ReturnCallback: " + "交换机:" + exchange);
log.info("ReturnCallback: " + "路由键:" + routingKey);
}
});
// 为 true 时,消息通过交换器无法匹配到队列时会返回给生产者,为 false 时,匹配不到会直接丢弃
rabbitTemplate.setMandatory(true);
// 设置发送时的转换 序列化
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
修改生产者代码,只需要发送消息即可:如下
运行结果为如下:并没有再次 爆出:Only one ConfirmCallback is supported by each RabbitTemplate 的问题
以上是演示 消息发送到交换机时交换机不存在报错,下面我们模拟 发送到交换机成功,发送到队列时候失败
消费者获取消息之后,确认已经发送到队列中