回退消息
在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如
果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。那么如何让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
简单说RoutingKey错误,交换机正常,会直接丢西消息。
1.yml配置
spring:
rabbitmq:
host: 192.168.64.137
port: 5672
username: guest
password: 123
virtual-host: /
# publisher-confirm-type: correlated
# 开启发布确认类型
# ⚫ NONE
# 禁用发布确认模式,是默认值
# ⚫ CORRELATED
# 发布消息成功到交换器后会触发回调方法
# ⚫ SIMPLE (发一条确认一条,不用)
# 经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法,
# 其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法
# 等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是
# waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker
publisher-confirms: true
# 路由不出去的消息会回退
publisher-returns: true
2.配置类
package com.cherry.rabbitmqttl.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置类 发布确认(高级)
*/
@Configuration
public class ConfirmConfig {
// 交换机
public static final String CONFIRM_EXCHANGE_NAME = "confirm_exchange";
// 队列
public static final String CONFIRM_QUEUE_NAME = "confirm_queue";
// routingKey
public static final String CONFIRM_ROUTING_KEY = "key1";
// 声明交换机
@Bean("confirmExchange")
public DirectExchange confirmExchange(){
return new DirectExchange(CONFIRM_EXCHANGE_NAME);
}
// 队列
@Bean("confirmQueue")
public Queue confirmQueue(){
return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
}
@Bean
public Binding queueBindingExchange(@Qualifier("confirmQueue")Queue confirmQueue,
@Qualifier("confirmExchange")DirectExchange confirmExchange){
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTING_KEY);
}
3.生产者
package com.cherry.rabbitmqttl.controller;
import com.cherry.rabbitmqttl.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试确认开始发消息
*/
@Slf4j
@RequestMapping("/confirm")
@RestController
public class ConfirmProductController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 交换机错误时交换机会显示交换机未收到消息的错误信息,
* routingkey错误时会显示交换机收到消息
* 发送消息内容,你好啊key1
* Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'confirm_exchange1' in vhost '/', class-id=60, method-id=40)
* 交换机还未收到 id 为:ID=1消息,由于原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'confirm_exchange1' in vhost '/', class-id=60, method-id=40)
* 送消息内容,你好啊key12
* 交换机已经收到 id 为:ID=2的消息
* @param message
*/
// 发消息
// ID唯一标识
// routingkey错误时没法获得失败回调
@GetMapping("/sendMsg/{message}")
public void sendMessage(@PathVariable String message){
// 正常发送消息: 交换机和队列都收到了
CorrelationData correlationData3 = new CorrelationData("ID=0");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,
ConfirmConfig.CONFIRM_ROUTING_KEY,
message,
correlationData3);
log.info("发送消息内容,{}", message + "key0" );
log.info("______________________________________________________________________");
// 交换机错误发送消息:交换机未收到,队列未收到
CorrelationData correlationData1 = new CorrelationData("ID=1");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME+1,
ConfirmConfig.CONFIRM_ROUTING_KEY + "key1",
message + "key1",
correlationData1);
log.info("发送消息内容,{}", message + "key1");
log.info("______________________________________________________________________");
// routingkey错误发送消息:交换机收到,队列未收到
CorrelationData correlationData2 = new CorrelationData("ID=2");
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE_NAME,
ConfirmConfig.CONFIRM_ROUTING_KEY + "2",
message + "key12",
correlationData2);
log.info("发送消息内容,{}", message + "key12");
}
}
4.消费者
package com.cherry.rabbitmqttl.consumer;
import com.cherry.rabbitmqttl.config.ConfirmConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ConfirmConsumer {
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE_NAME)
public void receiveConfirm(Message message){
String mesg = new String(message.getBody());
log.info("接收到的队列confirm.queue消息:{}", mesg);
}
}
5.回退回调函数
package com.cherry.rabbitmqttl.confirmcallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* 感知不能正常消费的消息回调
*/
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
// 注入
@Autowired
private RabbitTemplate rabbitTemplate;
// @PostConstruct注入功能再Autowired注入后注入
@PostConstruct
public void init(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
/**
* 交换机不管是否收到消息的一个回调方法
* CorrelationData
* 消息相关数据
* ack
* 交换机是否收到消息
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, java.lang.String cause) {
String id=correlationData!=null?correlationData.getId():"";
if(ack){
log.info("交换机已经收到 id 为:{}的消息",id);
}else{
log.info("交换机还未收到 id 为:{}消息,由于原因:{}",id,cause);
}
}
// 可以再当消息传递过程中不可路由时将消息返回给生产者(routingKey错误)
// 只有不可达目的时候,才进行回退
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("消息{},被交换机{}退回,退回的原因:{},路由Key{}",
new String(message.getBody()),
exchange,
replyText,
routingKey);
}
}
6.pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<groupId>com.cherry</groupId>
<artifactId>rabbitmqttl</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmqttl</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--RabbitMQ 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>