MQTT(Message Queuing Telemetry Transport)作为一种轻量级的发布/订阅消息传输协议,在物联网和消息推送场景中应用广泛。本文将详细介绍如何在SpringBoot项目中集成MQTT客户端,实现消息的发布和订阅功能。
在开始之前,我们需要准备以下环境:
提示:EMQ X 5.3.2是最后一个支持Windows的版本,后续版本仅支持Linux系统。
在开始编码前,有必要了解几个MQTT的核心概念:
从EMQ官网下载5.3.2版本:
bash复制https://packages.emqx.io/emqx-ce/v5.3.2/emqx-5.3.2-windows-amd64.zip
解压后,进入bin目录执行:
bash复制emqx start
EMQ X默认安装后没有配置访问控制,存在安全隐患,必须进行以下配置:
访问管理控制台:http://127.0.0.1:18083
创建客户端认证:
添加用户:
重要提示:生产环境务必配置TLS加密连接,本文演示使用明文连接仅用于开发测试。
推荐使用MQTTX作为测试客户端:
在pom.xml中添加必要依赖:
xml复制<dependencies>
<!-- MQTT核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- Web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
</dependencies>
application.yml配置:
yaml复制spring:
mqtt:
username: your_username
password: your_password
host-url: tcp://127.0.0.1:1883
client-id: springboot-client
default-topic: default/#
timeout: 100
keepalive: 100
topics:
- sensor/+
- device/status
创建MqttConfiguration配置类:
java复制@Configuration
@ConfigurationProperties("spring.mqtt")
public class MqttConfiguration {
private String username;
private String password;
private String hostUrl;
private String clientId;
private String defaultTopic;
private List<String> topics;
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setServerURIs(new String[]{hostUrl});
options.setCleanSession(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(20);
options.setAutomaticReconnect(true);
factory.setConnectionOptions(options);
return factory;
}
public String[] getAllTopics() {
List<String> allTopics = new ArrayList<>(topics);
allTopics.add(defaultTopic);
return allTopics.toArray(new String[0]);
}
}
java复制@Configuration
public class MqttInboundConfiguration {
@Autowired
private MqttConfiguration mqttConfig;
@Bean
public MessageChannel mqttInBoundChannel() {
return new DirectChannel();
}
@Bean
public MessageProducerSupport mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
mqttConfig.getClientId() + "-consumer",
mqttConfig.mqttClientFactory(),
mqttConfig.getAllTopics());
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(mqttInBoundChannel());
return adapter;
}
}
java复制@ServiceActivator(inputChannel = "mqttInBoundChannel")
public void handleMessage(Message<?> message) {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
String payload = message.getPayload().toString();
log.info("Received message from {}: {}", topic, payload);
// 根据不同的topic进行业务处理
if(topic.startsWith("sensor/")) {
processSensorData(payload);
} else if(topic.startsWith("device/")) {
processDeviceStatus(payload);
}
}
java复制@Configuration
public class MqttOutboundConfiguration {
@Autowired
private MqttConfiguration mqttConfig;
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(
mqttConfig.getClientId() + "-producer",
mqttConfig.mqttClientFactory());
handler.setAsync(true);
handler.setDefaultTopic(mqttConfig.getDefaultTopic());
handler.setDefaultQos(1);
return handler;
}
}
java复制@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
@Service
public class MqttSender {
@Autowired
private MqttGateway mqttGateway;
public void sendMessage(String topic, String message) {
mqttGateway.sendToMqtt(topic, message);
}
public void sendMessageWithQos(String topic, String message, int qos) {
mqttGateway.sendToMqtt(topic, qos, message);
}
}
java复制@RestController
public class TestController {
@Autowired
private MqttSender mqttSender;
@GetMapping("/send")
public String sendTestMessage() {
mqttSender.sendMessage("test/topic", "Hello MQTT");
return "Message sent";
}
}
访问/send接口后,使用MQTTX订阅test/topic主题,应该能收到发送的消息。
通过配置文件管理订阅主题,避免硬编码:
yaml复制spring:
mqtt:
topics:
- sensor/+
- device/status
- alarm/#
在配置类中读取并转换为数组:
java复制public String[] getAllTopics() {
List<String> allTopics = new ArrayList<>(topics);
allTopics.add(defaultTopic);
return allTopics.toArray(new String[0]);
}
java复制options.setAutomaticReconnect(true);
java复制options.setKeepAliveInterval(20); // 20秒
java复制options.setConnectionTimeout(10); // 10秒
java复制@Bean
public Executor mqttMessageExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("mqtt-handler-");
return executor;
}
@ServiceActivator(inputChannel = "mqttInBoundChannel")
@Async("mqttMessageExecutor")
public void handleMessage(Message<?> message) {
// 消息处理逻辑
}
java复制// 使用Redis等存储已处理消息ID
if(redisTemplate.opsForValue().setIfAbsent(messageId, "1", 5, TimeUnit.MINUTES)) {
// 处理消息
}
认证失败:
连接超时:
收不到消息:
消息重复:
高延迟:
高CPU/Memory:
安全加固:
监控告警:
集群部署:
消息持久化:
在实际项目中,我曾遇到一个因Client ID重复导致的消息丢失问题。后来我们实现了基于应用ID+实例ID+时间戳的Client ID生成规则,彻底解决了这个问题。这也提醒我们,在分布式环境中,Client ID的唯一性至关重要。