目录
前言
一、传统配置方式:繁琐且重复
1.1 传统代码示例
1.2 传统方式的痛点
二、统一配置方式:优雅且高效
2.1 统一配置代码解析
2.2 统一配置的优势
三、配置类深度解析:发送与接收
3.1 配置RabbitTemplate(发送端)
3.2 配置监听器工厂(接收端)
四、发送端与接收端实战:支持多种消息格式
4.1 发送端:两种方式示例
4.2 接收端:两种方式处理
五、总结
前言
在微服务架构中,RabbitMQ作为一款成熟的消息中间件,其应用非常广泛。然而,随着业务的发展,我们往往需要管理数十个乃至上百个队列和交换机。如果按照传统的做法,为每一个队列都编写一套独立的@Bean声明,配置类将会变得异常臃肿且难以维护。
本文将基于SpringBoot,介绍一种统一配置RabbitMQ队列、交换机和绑定的优雅方式。通过对比传统配置方式,你将深刻体会到统一配置在简化代码、提升可维护性方面的巨大优势。同时,文章还将结合生产实践,展示如何使用Protostuff进行高性能序列化,以及如何配置发送端和接收端。
关于Protostuff序列化工具类的详细实现和原理,可以参考我的另一篇文章:
Protostuff 序列化工具类详解:高性能Java对象序列化方案
一、传统配置方式:繁琐且重复
在早期的SpringBoot RabbitMQ配置中,最常见的做法是为每一个业务场景单独声明其所需的交换机、队列和绑定关系。
1.1 传统代码示例
@Configuration public class TraditionalRabbitMQConfig { // 业务A:直连交换机 @Bean public DirectExchange directExchangeForBizA() { return new DirectExchange("biz.a.exchange", true, false); } @Bean public Queue queueForBizA() { return new Queue("biz.a.queue", true); } @Bean public Binding bindingForBizA() { return BindingBuilder.bind(queueForBizA()) .to(directExchangeForBizA()) .with("biz.a.routing.key"); } // 业务B:扇出交换机 @Bean public FanoutExchange fanoutExchangeForBizB() { return new FanoutExchange("biz.b.exchange", true, false); } @Bean public Queue queueForBizB() { return new Queue("biz.b.queue", true); } @Bean public Binding bindingForBizB() { return BindingBuilder.bind(queueForBizB()) .to(fanoutExchangeForBizB()); } // ... 随着业务增加,这里会越来越长 }1.2 传统方式的痛点
代码臃肿:每增加一个业务,就需要增加至少3个
@Bean方法。配置分散:交换机、队列、路由键的名称通常硬编码在注解或字符串中,不易统一管理。
维护困难:当需要修改一个队列的名称或属性时,需要在整个配置类中搜索并修改,容易出错。
二、统一配置方式:优雅且高效
统一配置的核心思想是利用Spring AMQP提供的**Declarables**类,将所有需要声明的对象(交换机、队列、绑定)集中在一个@Bean方法中创建并返回。
2.1 统一配置代码解析
以下是您提供的配置类代码,它完美地展示了如何实现统一管理。
import com.cetcnav.rabbitMQListener.MyRabbitMQConfigProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; @Slf4j @Configuration public class RabbitMQConfig { @Autowired private MyRabbitMQConfigProperties rabbitMQProperties; // 配置属性类,集中管理名称 /** * 统一声明所有的交换机、队列和绑定关系。 * 这是统一配置的核心所在。 */ @Bean public Declarables unifiedDeclarations() { List<Declarable> declarables = new ArrayList<>(); // --- 业务1: tpos-位置业务 (Direct Exchange) --- DirectExchange exchangeDirectTpos = new DirectExchange( rabbitMQProperties.getExchange().getDirect().getTPos(), true, false); Queue queueTpos = new Queue(rabbitMQProperties.getQueue().getTPos(), true, false, false); Binding tposBinding = BindingBuilder.bind(queueTpos) .to(exchangeDirectTpos) .with(rabbitMQProperties.getRouting().getTPos()); declarables.add(exchangeDirectTpos); declarables.add(queueTpos); declarables.add(tposBinding); // --- 业务2: tlogin-登录登出业务 (Direct Exchange) --- DirectExchange exchangeDirectTlogin = new DirectExchange( rabbitMQProperties.getExchange().getDirect().getTLogin(), true, false); Queue queueTlogin = new Queue(rabbitMQProperties.getQueue().getTLogin(), true, false, false); Binding tloginBinding = BindingBuilder.bind(queueTlogin) .to(exchangeDirectTlogin) .with(rabbitMQProperties.getRouting().getTLogin()); declarables.add(exchangeDirectTlogin); declarables.add(queueTlogin); declarables.add(tloginBinding); // --- 业务3: pset-指令下发业务 (Fanout Exchange) --- FanoutExchange exchangeFanoutPset = new FanoutExchange( rabbitMQProperties.getExchange().getFanout().getPSet(), true, false); Queue queuePset = new Queue(rabbitMQProperties.getQueue().getPSet(), true, false, false); Binding psetBinding = BindingBuilder.bind(queuePset).to(exchangeFanoutPset); declarables.add(exchangeFanoutPset); declarables.add(queuePset); declarables.add(psetBinding); log.info("统一声明了 {} 个 RabbitMQ 组件 (交换机、队列、绑定)", declarables.size()); return new Declarables(declarables); } // ... (RabbitTemplate 和 监听器工厂的配置,下文会详细说明) }2.2 统一配置的优势
结构清晰:所有声明集中在一个方法内,业务逻辑一目了然。
易于维护:配合
MyRabbitMQConfigProperties配置类,所有名称统一管理。修改时只需改动配置文件,无需改动Java代码。减少样板代码:告别了大量的独立
@Bean方法,使配置类更加简洁。
三、配置类深度解析:发送与接收
一个完整的RabbitMQ配置,除了声明队列交换机,还需要配置发送端(RabbitTemplate)和接收端(监听器工厂)。
3.1 配置RabbitTemplate(发送端)
配置RabbitTemplate主要是为了设置消息转换器和添加发送回调,以便监控消息的送达情况。
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // 设置消息转换器,默认将对象转为JSON rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); // 确认消息是否成功发送到Exchange rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { log.info("消息[{}]发送到交换机成功", correlationData != null ? correlationData.getId() : "null"); } else { log.error("消息发送到交换机失败: {}", cause); } }); // 确认消息从Exchange路由到Queue是否失败 rabbitTemplate.setReturnsCallback(returned -> { log.error("消息无法路由到队列: 消息体: {}, 交换机: {}, 路由键: {}, 返回码: {}, 信息: {}", returned.getMessage(), returned.getExchange(), returned.getRoutingKey(), returned.getReplyCode(), returned.getReplyText()); }); return rabbitTemplate; }3.2 配置监听器工厂(接收端)
监听器工厂控制着消费者的行为,例如消息预取数量、确认模式等。
@Bean public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); factory.setConnectionFactory(connectionFactory); // 设置消息转换器,保证接收到的消息可以自动转换为对象(如果发送的是JSON) factory.setMessageConverter(new Jackson2JsonMessageConverter()); // 关键配置:prefetch=1,确保每次只消费一条消息,处理完后再取下一条,实现公平分发 factory.setPrefetchCount(1); // 设置手动确认模式,由业务代码控制消息的ack // factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); return factory; }四、发送端与接收端实战:支持多种消息格式
在实际项目中,我们可能需要发送不同格式的消息,例如纯文本JSON字符串,或者经过Protostuff压缩的二进制数据。
4.1 发送端:两种方式示例
// 方式一:发送序列化后的二进制数据 (Protostuff) public void sendWithProtostuff(Object form) { byte[] data = ProtostuffSerializer.serialize(form); MessageProperties properties = new MessageProperties(); properties.setContentType("application/x-protostuff"); // 自定义类型,便于接收方识别 properties.setContentLength(data.length); Message message = new Message(data, properties); // 对于Fanout交换机,路由键填空字符串 rabbitTemplate.convertAndSend(rabbitMQProperties.getExchange().getFanout().getPSet(), "", message); log.info("已发送Protostuff序列化消息至 {}", rabbitMQProperties.getExchange().getFanout().getPSet()); } // 方式二:发送JSON字符串 (适用于Direct/直连交换机) public void sendWithJson(Object toMqtt) { String jsonMessage = JSONObject.toJSONString(toMqtt); rabbitTemplate.convertAndSend( rabbitMQProperties.getExchange().getDirect().getTPos(), rabbitMQProperties.getRouting().getTPos(), jsonMessage ); log.info("已发送JSON消息至 {}", rabbitMQProperties.getExchange().getDirect().getTPos()); }4.2 接收端:两种方式处理
在消费者端,通过@RabbitListener接收消息。根据消息的内容类型,可以采用不同的处理方式。
import org.springframework.amqp.core.Message; import com.alibaba.fastjson.JSONObject; @Component @Slf4j public class MessageConsumer { /** * 接收并反序列化Protostuff格式的消息 * 监听的是Fanout交换机绑定的队列 */ @RabbitListener(queues = "${my-rabbitmq-config.queue.p-set}") public void pSetListener(Message message) { try { byte[] body = message.getBody(); // 调用Protostuff工具类反序列化 PsetDataVO psetDataVO = ProtostuffSerializer.deserialize(body, PsetDataVO.class); log.info("接收到pset信息:【{}】", JSONObject.toJSONString(psetDataVO)); // ... 处理业务逻辑 } catch (Exception e) { log.error("处理pset消息失败", e); } } /** * 接收JSON字符串格式的消息 * 监听的是Direct交换机绑定的队列 */ @RabbitHandler @RabbitListener(queues = "${my-rabbitmq-config.queue.t-pos}") public void tposListener(Message message) { // 直接获取消息体并转为字符串,因为发送的是JSON字符串 String msg = new String(message.getBody()); log.info("接收到tpos消息:{}", msg); // 如果需要转为对象,可以使用 JSONObject.parseObject(msg, TargetClass.class) // ... 处理业务逻辑 } }五、总结
本文通过对比,展示了SpringBoot中RabbitMQ的两种配置方式。统一配置结合外部化配置属性,极大地简化了多队列、多交换机的管理,是构建大型、可维护系统的推荐实践。
同时,通过结合高性能的Protostuff序列化工具,我们可以在消息中间件传输中进一步压缩数据体积,提升系统吞吐量。希望本文对你优化RabbitMQ的使用有所帮助。