rabbitmq整合springboot实现发布确认
rabbitmq整合springboot实现发布确认
导包
<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>
<!--RabbitMQ 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-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>
配置文件
spring:
rabbitmq:
host: 192.168.84.131
port: 5672
username: admin
password: 123
# 开启发布确认,NONE:禁用发布确认模式,是默认值;CORRELATED:开启发布确认;SIMPLE:经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法;其二在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker(即有一条消息发布确认失败则后面的消息无法被发送)
publisher-confirm-type: correlated
# 开启消息回退
publisher-returns: true
发布确认配置类
package com.yl.config;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 发布确认队列配置类
*
* @Y-wee
*/
@Configuration
public class ConfirmConfig {
/**
* 交换机名称
*/
public static final String CONFIRM_EXCHANGE = "confirmExchange";
/**
* 队列名称
*/
public static final String CONFIRM_QUEUE = "confirmQueue";
/**
* 路由
*/
public static final String CONFIRM_ROUTINGKEY = "confirmRoutingKey";
/**
* 声明交换机
*
* @return
*/
@Bean
public DirectExchange confirmExchange() {
return new DirectExchange(CONFIRM_EXCHANGE);
}
/**
* 声明队列
*
* @return
*/
@Bean
public Queue confirmQueue() {
return new Queue(CONFIRM_QUEUE);
}
/**
* 将交换机与队列进行绑定
*
* @param confirmExchange
* @param confirmQueue
* @return
*/
@Bean
public Binding confirmBinding(@Qualifier("confirmExchange") DirectExchange confirmExchange,
@Qualifier("confirmQueue") Queue confirmQueue) {
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTINGKEY);
}
}
发布确认回调接口及消息回退接口实现类
package com.yl.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
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;
/**
* 发布确认回调接口及消息回退接口实现类
*
* @author Y-wee
*/
@Slf4j
@Component
public class MyCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 将发布确认回调接口及消息回退接口实现类注入到RabbitTemplate
*
* @PostConstruct用法: PostConstruct注解用于在依赖关系注入完成之后需要执行的方法上, 以执行任何初始化
* 此注解必须在将类放入服务之前调用
* 被@PostConstruct修饰的方法会在服务器加载Servlet的时候(servlet构造方法之后,init方法之前)运行
* 并且只会被服务器执行一次
* 被注解的方法必须满足:
* 1、非静态方法
* 2、方法不得有任何参数
* 3、方法返回值必须为void
* 4、方法不得抛出已检查异常
*/
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
/**
* 发布确认回调接口核心方法
*
* @param correlationData 消息相关数据,由生产者发送
* @param ack 交换机是否收到消息,true:收到消息;false:没有收到消息
* @param cause 交换机没有收到消息的原因,当ack=true时为null
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("交换机收到id={}的消息", correlationData.getId());
} else {
log.info("交换机没有收到id={}的消息,原因:{}", correlationData.getId(), cause);
}
}
/**
* 消息回退接口核心方法
* 当队列没有接收到消息时执行
*
* @param returned 回退消息相关数据
*/
@Override
public void returnedMessage(ReturnedMessage returned) {
log.info("消息被交换机退回,message:{},exchange:{},routingKey:{},replyCode:{},replyText:{}",
new String(returned.getMessage().getBody()),
returned.getExchange(),
returned.getRoutingKey(),
returned.getReplyCode(),
returned.getReplyText());
}
}
消息生产者
package com.yl.controller;
import com.yl.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;
/**
* 发布确认队列消息生产者
*
* @author Y-wee
*/
@Slf4j
@RestController
@RequestMapping("/confirm")
public class SendConfirmMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
*
* @param message
*/
@GetMapping("/sendMessage/{message}")
public void sendMessage(@PathVariable String message) {
// 设置消息id
CorrelationData ExchangeFail = new CorrelationData("1");
CorrelationData ExchangeSucceed = new CorrelationData("2");
CorrelationData QueueFail = new CorrelationData("3");
// 设置错误的交换机模拟交换机没有收到消息(发布确认失败)
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE+"exchange", ConfirmConfig.CONFIRM_ROUTINGKEY, message,ExchangeFail);
// 模拟交换机收到消息(发布确认成功)
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, ConfirmConfig.CONFIRM_ROUTINGKEY, message,ExchangeSucceed);
// 设置错误的路由模拟队列没有收到消息(执行消息回退方法)
rabbitTemplate.convertAndSend(ConfirmConfig.CONFIRM_EXCHANGE, ConfirmConfig.CONFIRM_ROUTINGKEY+"key", message,QueueFail);
log.info("消息发送完毕");
}
}
消息消费者
package com.yl.consumer;
import com.yl.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;
/**
* 发布确认队列消息消息消费者
*
* @author Y-wee
*/
@Slf4j
@Component
public class ConfirmConsumer {
/**
* 消费消息
*
* @param message
*/
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE)
public void consumer(Message message) {
String info = new String(message.getBody());
log.info("接收到消息:{}", info);
}
}
至此,发布确认代码就写完了
但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然后触发报警,再来手动处理
而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错
解决方案:备份交换机
在 RabbitMQ 中,有一种备份交换机的机制存在,备份交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时,就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都进入这个队列了。当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警
代码架构图:
要实现上面代码架构图只需要在发布确认代码基础修改发布确认配置类ConfirmConfig和消息消费者ConfirmConsumer
发布确认配置类
package com.yl.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;
/**
* 发布确认队列配置类
*
* @Y-wee
*/
@Configuration
public class ConfirmConfig {
/**
* 发布确认交换机名称
*/
public static final String CONFIRM_EXCHANGE = "confirmExchange";
/**
* 备份交换机
*/
public static final String BACKUP_EXCHANGE = "backupExchange";
/**
* 发布确认队列名称
*/
public static final String CONFIRM_QUEUE = "confirmQueue";
/**
* 备份队列
*/
public static final String BACKUP_QUEUE = "backupQueue";
/**
* 警告队列
*/
public static final String WARNING_QUEUE = "warningQueue";
/**
* 发布确认交换机路由
*/
public static final String CONFIRM_ROUTINGKEY = "confirmRoutingKey";
/**
* 声明发布确认交换机,并设置备份交换机名称
*
* @return
*/
@Bean
public DirectExchange confirmExchange() {
return ExchangeBuilder.directExchange(CONFIRM_EXCHANGE).
withArgument("alternate-exchange", BACKUP_EXCHANGE)
.build();
}
/**
* 声明备份交换机
*
* @return
*/
@Bean
public FanoutExchange backupExchange() {
return new FanoutExchange(BACKUP_EXCHANGE);
}
/**
* 声明队列
*
* @return
*/
@Bean
public Queue confirmQueue() {
return new Queue(CONFIRM_QUEUE);
}
/**
* 声明备份队列
*
* @return
*/
@Bean
public Queue backupQueue() {
return QueueBuilder.durable(BACKUP_QUEUE).build();
}
/**
* 声明警告队列
*
* @return
*/
@Bean
public Queue warningQueue() {
return new Queue(WARNING_QUEUE);
}
/**
* 将交换机与队列进行绑定
*
* @param confirmExchange
* @param confirmQueue
* @return
*/
@Bean
public Binding confirmBinding(@Qualifier("confirmExchange") DirectExchange confirmExchange,
@Qualifier("confirmQueue") Queue confirmQueue) {
return BindingBuilder.bind(confirmQueue).to(confirmExchange).with(CONFIRM_ROUTINGKEY);
}
/**
* 将备份交换机和备份队列进行绑定
*
* @param backupExchange
* @param backupQueue
* @return
*/
@Bean
public Binding backupBinding(@Qualifier("backupExchange") FanoutExchange backupExchange,
@Qualifier("backupQueue") Queue backupQueue) {
return BindingBuilder.bind(backupQueue).to(backupExchange);
}
/**
* 将备份交换机和警告队列进行绑定
*
* @param backupExchange
* @param warningQueue
* @return
*/
@Bean
public Binding warningBinding(@Qualifier("backupExchange") FanoutExchange backupExchange,
@Qualifier("warningQueue") Queue warningQueue) {
return BindingBuilder.bind(warningQueue).to(backupExchange);
}
}
消息消费者
package com.yl.consumer;
import com.yl.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;
/**
* 发布确认队列消息消息消费者
*
* @author Y-wee
*/
@Slf4j
@Component
public class ConfirmConsumer {
/**
* 消费消息
*
* @param message
*/
@RabbitListener(queues = ConfirmConfig.CONFIRM_QUEUE)
public void consumer(Message message) {
String info = new String(message.getBody());
log.info("接收到发布确认队列消息:{}", info);
}
/**
* 消费备份队列消息
*
* @param message
*/
@RabbitListener(queues = ConfirmConfig.BACKUP_QUEUE)
public void backupConsumer(Message message) {
String info = new String(message.getBody());
log.info("接收到备份队列消息:{}", info);
}
/**
* 消费警告队列消息
*
* @param message
*/
@RabbitListener(queues = ConfirmConfig.WARNING_QUEUE)
public void warningConsumer(Message message) {
String info = new String(message.getBody());
log.info("接收到警告队列消息:{}", info);
}
}
发布确认消息回退和备份交换机两者同时开启时,备份交换机优先级高
记得快乐
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~