文章目录
前言
本文是工作之余的随手记,记录在工作期间使用 RabbitMQ
的笔记。
1、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'
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>
2、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() {
}
}
3、MqMessage
MQ
消息实体类
package com.wen.mq;
import lombok.Data;
@Data
public class MqMessage<T> {
private String msgType;
private String msgOrigin;
private long time;
private T data;
}
4、MqMessageItem
MQ
消息实体类
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;
}
5、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());
}
}
6、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);
}
}
}
7、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会重新放回队列
}
}
}