本文是工作之余的随手记,记录在工作期间使用 RabbitMQ
的笔记。
文章目录
1、实现注意细节
- 1、使用
@Bean
定义Queue
、Exchange
和Binding
,Spring Boot 会在启动时自动声明这些组件。 若需动态配置,可通过RabbitAdmin
管理组件生命周期。 - 2、发送消息有多种解决方式,本文选用实现
SimpleMessageListenerContainer
类,也可以使用注入RabbitTemplate
,直接调用convertAndSend
方法发送消息,支持自动消息转换(如 JSON 序列化)。 - 3、消费消息有多种解决方式,本文选用实现
ChannelAwareMessageListener
接口,也可以使用在方法上使用@RabbitListener
注解监听队列,支持消息体自动反序列化为对象。 - 4、2条和3条所实现的 RabbitMQ 的代码示例如下:
SpringBoot 使用 spring-boot-starter-amqp 起步依赖实现 RabbitMQ 代码示例(二)
-
5、在启动类添加
@EnableRabbit
注解,可以激活监听器。 -
6、在实现过程中可以配置一些高级特性:
- 消息确认模式(Ack/Nack)
- 重试机制(Retry Template)
- 死信队列(Dead Letter Exchange)
- 多消费者并发配置
2、实现注意事项
- 1、敏感信息加密:避免明文存储密码,最好有密码服务等。
- 2、持久化设置:队列、交换机、消息均需设置为持久化(Durable),防止服务重启丢失数据。
- 3、本文选用的手动确认模式,处理完成后需显式调用
channel.basicAck()
或basicNack()
。SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory); container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
- 4、性能优化连接池配置,调整
spring.rabbitmq.cache
相关参数,优化 Channel 和 Connection 复用。spring.rabbitmq.listener.simple.concurrency=5 spring.rabbitmq.listener.simple.max-concurrency=10
- 5、批量消费:使用
@RabbitListener
批量接收消息提升吞吐量。 - 6、异常处理:常见于使用死信队列,捕获处理失败的消息,避免消息无限重试。
- 7、如果需要使用重试机制,可以配置重试策略,配置
RetryTemplate
控制重试次数和退避策略。
通过遵循以上步骤与注意事项,可快速构建高可靠、易维护的 RabbitMQ 消息系统,充分发挥 Spring Boot 自动化配置的优势,同时规避常见风险。
3、配置文件:application.yml
使用 use
属性,方便随时打开和关闭使用 MQ
,并且可以做到细化控制。
spring:
rabbitmq:
use: true
host: 10.100.10.100
port: 5672
username: wen
password: 123456
exchangeSubPush: 'exWen'
queueSubPush: 'ha.queue.SubPush'
routeSubPush: '1000'
exchangeState: sync.ex.State
queueState: ha.q.Server
queueStateSync: ha.q.StateServer
routeState: state
exchangeOnlineMonitor: 'sync.ex.State'
routeOnlineMonitor: 'state'
queueOnlineMonitor: 'ha.q.Online'
4、依赖文件:pom.xml
pom.xml
文件中使用的是 SpringBoot
项目,使用 spring-boot-starter-amqp
依赖。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wen</groupId>
<artifactId>springboot-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
</project>
5、配置类:RabbitMqConfig
配置类,将可配置的参数使用 @Value
做好配置,与 application.yml
相互对应。
package com.wen.mq;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Slf4j
@Configuration
@Data
public class RabbitMqConfig {
@Value("${spring.rabbitmq.use:true}")
private boolean use;
@Value("${spring.rabbitmq.host}")
private String host;
@Value("${spring.rabbitmq.port}")
private int port;
@Value("${spring.rabbitmq.username}")
private String username;
@Value("${spring.rabbitmq.password}")
private String password;
@Value("${spring.rabbitmq.virtual-host:}")
private String virtualHost;
@Value("${spring.rabbitmq.exchangeState}")
private String exchangeState;
@Value("${spring.rabbitmq.queueState}")
private String queueState;
@Value("${spring.rabbitmq.routeState}")
private String routeState;
@Value(("${spring.rabbitmq.queueStateSync}"))
private String queueStateSync;
@Value("${spring.rabbitmq.exchangeOnlineInfo}")
private String exchangeOnlineInfo;
@Value("${spring.rabbitmq.routeOnlineInfo}")
private String routeOnlineInfo;
@Value("${spring.rabbitmq.queueOnlineInfo}")
private String queueOnlineInfo;
@PostConstruct
private void init() {
}
}
6、消息类:MqMessage
package com.wen.mq;
import lombok.Data;
@Data
public class MqMessage<T> {
private String msgType;
private String msgOrigin;
private long time;
private T data;
}
7、消息类:MqMessageItem
package com.wen.mq;
import lombok.Data;
@Data
public class MqMessageItem {
private long userId;
private String userName;
private int userAge;
private String userSex;
private String userPhone;
private String op;
}
8、配置中心类:DirectMode
配置中心:使用 SimpleMessageListenerContainer
进行配置。新加一个消费者队列就要在这里进行配置。
package com.wen.mq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class DirectMode {
@Autowired
RabbitMqConfig rabbitMqConfig;
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private StateConsumer stateConsumer;
@Autowired
private InfoConsumer infoConsumer;
@Bean
public SimpleMessageListenerContainer initMQ() {
if (!rabbitMqConfig.isUse()) {
return null;
}
log.info("begin!");
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setConcurrentConsumers(1);
container.setMaxConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认
// 设置一个队列
container.setQueueNames(rabbitMqConfig.getQueueStateSync());
//如果同时设置多个队列如下: 前提是队列都是必须已经创建存在的
//container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3”);
//另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
//container.setQueues(new Queue("TestDirectQueue",true));
//container.addQueues(new Queue("TestDirectQueue2",true));
//container.addQueues(new Queue("TestDirectQueue3",true));
container.setMessageListener(stateConsumer);
log.info("end");
return container;
}
@Bean
public SimpleMessageListenerContainer contactSyncContainer() {
if (!rabbitMqConfig.isUse()) {
return null;
}
log.info("contact begin");
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setConcurrentConsumers(1);
container.setMaxConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
//设置一个队列
container.setQueueNames(rabbitMqConfig.getQueueOnlineInfo());
container.setMessageListener(infoConsumer);
log.info("contact end");
return container;
}
@Bean
public Queue queueState() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return new Queue(rabbitMqConfig.getQueueState());
}
@Bean
public Queue queueStateSync() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return new Queue(rabbitMqConfig.getQueueStateSync());
}
@Bean
DirectExchange exchangeState() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return new DirectExchange(rabbitMqConfig.getExchangeState());
}
@Bean
Binding bindingState() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return BindingBuilder.bind(queueState()).to(exchangeState()).with(rabbitMqConfig.getRouteState());
}
@Bean
Binding bindingStateSync() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return BindingBuilder.bind(queueStateSync()).to(exchangeState()).with(rabbitMqConfig.getRouteState());
}
// 新加一个消费者
@Bean
public Queue queueOnlineMonitor() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return new Queue(rabbitMqConfig.getQueueOnlineInfo());
}
@Bean
DirectExchange exchangeOnlineMonitor() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return new DirectExchange(rabbitMqConfig.getExchangeOnlineInfo());
}
@Bean
Binding bindingExchangeOnlineMonitor() {
if (!rabbitMqConfig.isUse()) {
return null;
}
return BindingBuilder.bind(queueOnlineMonitor()).to(exchangeOnlineMonitor()).with(rabbitMqConfig.getRouteOnlineInfo());
}
}
9、消费者类1:StateConsumer
实现 ChannelAwareMessageListener
接口,可以在这里面做相应的操作,例如存缓存,存库等。
package com.wen.mq;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component
public class StateConsumer implements ChannelAwareMessageListener {
@Autowired
RabbitMqConfig rabbitMqConfig;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String queueName = message.getMessageProperties().getConsumerQueue();
long deliveryTag = message.getMessageProperties().getDeliveryTag();
if (!rabbitMqConfig.getQueueStateSync().equals(queueName)) {
String bodyStr = new String(message.getBody(), StandardCharsets.UTF_8);
try {
MqMessage<List<MqMessageItem>> mqMessage =
JSON.parseObject(bodyStr, new TypeReference<MqMessage<List<MqMessageItem>>>() {});
// 这里可以对消息做其他处理,例如存储到缓存中
List<MqMessageItem> items = mqMessage.getData();
if (CollectionUtil.isNotEmpty(items)) {
applyToRedis(mqMessage);
}
log.info("consume mq msg ok, queue:{}, deliveryTag:{}, msg:{}", queueName, deliveryTag, mqMessage);
channel.basicAck(deliveryTag, false);
} catch (JSONException e) {
log.error("parse mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
channel.basicReject(deliveryTag, false);
} catch (Exception e) {
log.error("consume mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
channel.basicReject(deliveryTag, true); //为true会重新放回队列
}
}
}
public static final String MQ_STATE_OP_REMOVE_STATE = "REMOVE_STATE";
public static final String MQ_STATE_OP_CHANGE_STATE = "CHANGE_STATE";
private void applyToRedis(MqMessage<List<MqMessageItem>> mqMessage) {
List<MqMessageItem> data = mqMessage.getData();
Map<String, List<MqMessageItem>> itemGroupByOp =
data.stream().collect(Collectors.groupingBy(item -> item.getOp()));
List<MqMessageItem> stateToRemove = itemGroupByOp.get(MQ_STATE_OP_REMOVE_STATE);
List<MqMessageItem> stateToChange = itemGroupByOp.get(MQ_STATE_OP_CHANGE_STATE);
if (CollectionUtil.isNotEmpty(stateToRemove)) {
Map<Long, Set<String>> map = new HashMap<>();
for (MqMessageItem item : stateToRemove) {
map.computeIfAbsent(item.getUserId(), u -> new HashSet<>())
.add(String.valueOf(item.getUserAge()));
}
// cacheService.removeUserState(map);
}
if (CollectionUtil.isNotEmpty(stateToChange)) {
List<MqMessageItem> list = stateToChange.stream().map(u -> {
MqMessageItem dto = new MqMessageItem();
dto.setUserId(u.getUserId());
dto.setUserAge(u.getUserAge());
dto.setUserName(u.getUserName());
dto.setUserSex(u.getUserSex());
dto.setUserPhone(u.getUserPhone());
return dto;
}).collect(Collectors.toList());
// cacheService.saveUserState(list);
}
}
}
10、消费者类2:InfoConsumer
实现 ChannelAwareMessageListener
接口,可以在这里面做相应的操作,例如存缓存,存库等。
package com.wen.mq;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class InfoConsumer implements ChannelAwareMessageListener {
@Autowired
RabbitMqConfig rabbitMqConfig;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
String queueName = message.getMessageProperties().getConsumerQueue();
log.info("queueName: {}", queueName);
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
byte[] body = message.getBody();
String content = new String(body);
MqMessage msg = JSONObject.parseObject(content, MqMessage.class);
if (rabbitMqConfig.getQueueOnlineInfo().equals(queueName)) {
// 订阅到的消息就是变更的消息
// 这里可使用service对消息进行消费,返回一个boolean
log.info("用户监控数据写入失败!数据:{}", msg);
}
log.info("consume mq msg ok, queue:{}, deliveryTag:{}, msg:{}", queueName, deliveryTag, msg);
channel.basicAck(deliveryTag, false);
} catch (JSONException e) {
log.error("parse mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
channel.basicReject(deliveryTag, false); //为true会重新放回队列
} catch (Exception e) {
log.error("consume mq msg exception, queue:{}, deliveryTag:{}", queueName, deliveryTag, e);
channel.basicReject(deliveryTag, true); //为true会重新放回队列
}
}
}
注意:本文是使用 SpringBoot 项目为核心进行开发,所使用的依赖和类都如上展示,可以应用于基本业务逻辑。