docker部署搭建以及java实现mqtt流程

26 阅读3分钟

前提

最近公司开发需要使用mqtt对接G7物流轨迹数据。记录一下自己使用docker部署搭建mqtt的整个核心流程和java代码。

本地安装 启动emqx

第一步 docker安装 emqx

docker pull emqx/emqx:latest

第二步 创建容器实例

docker run -d --name emqx --privileged=true -p 1883:1883 -p 8883:8883 -p 8083:8083 -p 8084:8084 -p 8081:8081 -p 18083:18083 emqx/emqx

启动之后登录mqtt控制台,配置完客户端认证和用户之后。

1.连接配置

image.png

2.订阅者配置(这里可以进行测试,左边发布消息之后右边就会接收到消息)

image.png

java实现

引入pom依赖

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>

新增配置文件,如不需要对接G7则可自行定义url、账号、密码等配置数据


/**
 * MQTT配置属性类
 * 基于G7易流MQTT推送服务配置
 */
@ConfigurationProperties(prefix = "g7.mqtt")
@RefreshScope
@Configuration
@Data
@Slf4j
public class MqttProperties implements Serializable {

    /**
     * MQTT服务器地址 (V1: tcp://openapi.huoyunren.com:1883,
     * V2: tcp://mqtt.openapi.huoyunren.com:8000)
     */
    private String brokerUrl = "tcp://127.0.0.1:1883";

    /**
     * 客户端标识符,由G7易流系统分配
     */
    private String clientId = "emqx_MDE0Nz";

    /**
     * 访问ID,对应MQTT连接的username,由G7易流系统分配
     */
    private String accessId = "test";

    /**
     * 访问密钥,对应MQTT连接的password,由G7易流系统分配
     */
    private String secret = "test";

    /**
     * 订阅主题,由G7易流系统分配
     */
    private String topic = "testtopic/1";

    /**
     * 服务质量等级 (0: 最多一次, 1: 至少一次, 2: 恰好一次)
     * G7易流推荐使用1或0,qos2消耗较大
     */
    private int qos = 1;

    /**
     * 连接超时时间(秒)
     */
    private int connectionTimeout = 30;

    /**
     * 自动重连间隔(秒)
     */
    private int automaticReconnectInterval = 60;

    /**
     * 是否清除会话
     * true: 清除会话,服务器发送topic的最新一条数据给客户端
     * false: 保留会话,服务器将从上次访问的位置发送数据
     */
    private boolean cleanSession = false;

    /**
     * 是否启用自动重连
     */
    private boolean automaticReconnect = true;

    /**
     * 完成消息最大飞行窗口
     */
    private int maxInflight = 100;


    /**
     * 是否实例化mqtt 0关闭1开启
     */
    private Integer mqttEnabled = 1;


    /**
     * 共享消费组名称 区分不同环境
     */
    private String groupName = "$share/ztc-group-dev/";

    /**
     * 创建MQTT客户端工厂
     */
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();

        options.setServerURIs(new String[] { brokerUrl });
        options.setUserName(accessId);
        options.setPassword(secret.toCharArray());
        options.setCleanSession(cleanSession);
        options.setAutomaticReconnect(automaticReconnect);
        options.setConnectionTimeout(connectionTimeout);
        options.setMaxInflight(maxInflight);

        // 设置自动重连间隔
       /* if (automaticReconnect) {
            options.setAutomaticReconnectDelay(automaticReconnectInterval * 1000);
        }
        */
        // 设置遗嘱消息(可选)
        // options.setWill("client/disconnected", "offline".getBytes(), 1, false);

        factory.setConnectionOptions(options);
        log.info("MQTT客户端工厂初始化完成,服务器地址: {}, 客户端ID: {}", brokerUrl, clientId);
        return factory;
    }

mqtt实例 实现类

/**
 * MQTT消息接收服务实现类
 * 基于G7易流MQTT推送服务
 */
@Slf4j
@Service
@RefreshScope
public class MqttServiceImpl implements MqttService {
    @Autowired
    private MqttProperties mqttProperties;

    @Autowired
    private MqttMessageProcessor messageProcessor;

    private MqttClient mqttClient;

    @Autowired
    private MqttPahoClientFactory mqttClientFactory;

    @PostConstruct
    public void init() {

        if (mqttProperties.getMqttEnabled() == 1) {


            try {

                // 创建MQTT客户端
                mqttClient = new MqttClient(
                        mqttProperties.getBrokerUrl(),
                        mqttProperties.getClientId(),
                        new MemoryPersistence()
                );

                // 设置回调
                mqttClient.setCallback(new MqttCallback() {
                    @Override
                    public void connectionLost(Throwable cause) {
                        log.error("MQTT连接断开: {}", cause.getMessage(), cause);
                        try {
                            //判断是否失败 失败则重连11
                            if (!mqttClient.isConnected()) {
                                connect();
                            }
                            Thread.sleep(200);

                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }

                    @Override
                    public void messageArrived(String topic, MqttMessage message) throws Exception {
                        log.info("收到MQTT消息,主题: {}, 消息ID: {}, QoS: {}",
                                topic, message.getId(), message.getQos());

                        // 解析消息内容
                        String payload = new String(message.getPayload(), "UTF-8");
                        log.debug("消息内容: {}", payload);

                        try {
                            // 这里可以添加消息处理逻辑
                            processMessage(topic, payload);
                        } catch (Exception e) {
                            log.error("处理MQTT消息失败: {}", e.getMessage(), e);
                        }
                    }

                    @Override
                    public void deliveryComplete(IMqttDeliveryToken token) {
                        log.debug("消息发送完成: {}", token.getMessageId());
                    }
                });

                log.info("MQTT服务初始化完成,客户端ID: {}", mqttProperties.getClientId());

                // 自动连接到MQTT服务器
                connect();
            } catch (Exception e) {
                log.error("初始化MQTT服务失败: {}", e.getMessage(), e);
            }

        }
    }

    @Override
    public boolean connect() {
        try {
            if (mqttClient == null) {
                log.error("MQTT客户端未初始化");
                return false;
            }

            if (mqttClient.isConnected()) {
                log.info("MQTT客户端已连接");
                return true;
            }

            log.info("正在连接MQTT服务器: {}", mqttProperties.getBrokerUrl());
            mqttClient.connect(mqttClientFactory.getConnectionOptions());

            // 连接成功后订阅主题
            if (!mqttProperties.getTopic().isEmpty()) {
                String[] split = mqttProperties.getTopic().split(",");
                for (String topic : split) {
                    subscribe(mqttProperties.getGroupName() + topic, mqttProperties.getQos());
                }
            }

            log.info("MQTT服务器连接成功");
            return true;
        } catch (Exception e) {
            log.error("连接MQTT服务器失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public void disconnect() {
        try {
            if (mqttClient != null && mqttClient.isConnected()) {
                mqttClient.disconnect();
                log.info("MQTT连接已断开");
            }
        } catch (Exception e) {
            log.error("断开MQTT连接失败: {}", e.getMessage(), e);
        }
    }

    @Override
    public boolean subscribe(String topic, int qos) {
        try {
            if (mqttClient == null || !mqttClient.isConnected()) {
                log.error("MQTT客户端未连接,无法订阅主题");
                return false;
            }

            log.info("订阅MQTT主题: {}, QoS: {}", topic, qos);
            mqttClient.subscribe(topic, qos);
            return true;
        } catch (Exception e) {
            log.error("订阅MQTT主题失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public void unsubscribe(String topic) {
        try {
            if (mqttClient == null || !mqttClient.isConnected()) {
                log.error("MQTT客户端未连接,无法取消订阅主题");
                return;
            }

            log.info("取消订阅MQTT主题: {}", topic);
            mqttClient.unsubscribe(topic);
        } catch (Exception e) {
            log.error("取消订阅MQTT主题失败: {}", e.getMessage(), e);
        }
    }

    @Override
    public boolean publish(String topic, String message, int qos) {
        try {
            if (mqttClient == null || !mqttClient.isConnected()) {
                log.error("MQTT客户端未连接,无法发布消息");
                return false;
            }

            MqttMessage mqttMessage = new MqttMessage(message.getBytes("UTF-8"));
            mqttMessage.setQos(qos);

            log.info("发布MQTT消息,主题: {}, QoS: {}", topic, qos);
            mqttClient.publish(topic, mqttMessage);
            return true;
        } catch (Exception e) {
            log.error("发布MQTT消息失败: {}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public boolean isConnected() {
        return mqttClient != null && mqttClient.isConnected();
    }

    @PreDestroy
    public void destroy() {
        disconnect();
        try {
            if (mqttClient != null) {
                mqttClient.close();
            }
        } catch (Exception e) {
            log.error("关闭MQTT客户端失败: {}", e.getMessage(), e);
        }
    }

    /**
     * 处理接收到的MQTT消息
     * @param topic 主题
     * @param payload 消息内容
     */
    private void processMessage(String topic, String payload) {
        log.info("处理MQTT消息,主题: {}, 内容: {}", topic, payload);


//      String[] supportedTopics = messageProcessor.getSupportedTopics();

        String[] supportedTopics = mqttProperties.getTopic().split(",");

        if (supportedTopics != null) {
            for (String supportedTopic : supportedTopics) {
                //判断是否是  配置的订阅主题才进行处理
                if (topic.contains(supportedTopic) || topic.equals(supportedTopic)) {
                    try {
                        MqttMessageResponseVO result = messageProcessor.processMessage(topic, payload);
                        log.info("消息处理器处理结果: {}", result);
                    } catch (Exception e) {
                        log.error("消息处理器处理消息失败: {}", e.getMessage(), e);
                    }
                    break;
                }
            }

        } else {
            // 默认处理逻辑
            try {
                Object messageObj = JSON.parse(payload);
                log.info("默认处理MQTT消息: {}", messageObj);
            } catch (Exception e) {
                log.error("解析MQTT消息失败: {}", e.getMessage(), e);
            }
        }
    }

controller层


/**
 * MQTT消息接收控制器
 * 基于G7易流MQTT推送服务
 */
@Slf4j
@RestController
@RequestMapping("/mqtt")
@Tag(name = "MQTT消息接收", description = "MQTT消息接收服务API")
public class MqttController {

    @Autowired
    private MqttService mqttService;

    /**
     * 连接MQTT服务器
     */
    @PostMapping("/connect")
    @Operation(summary = "连接MQTT服务器", description = "连接到G7易流MQTT服务器")
    public MqttMessageResponseVO connect() {
        try {
            boolean result = mqttService.connect();
            if (result) {
                return MqttMessageResponseVO.success("MQTT服务器连接成功");
            } else {
                return MqttMessageResponseVO.failure("MQTT服务器连接失败");
            }
        } catch (Exception e) {
            log.error("连接MQTT服务器异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("连接MQTT服务器异常: " + e.getMessage());
        }
    }

    /**
     * 断开MQTT连接
     */
    @PostMapping("/disconnect")
    @Operation(summary = "断开MQTT连接", description = "断开与G7易流MQTT服务器的连接")
    public MqttMessageResponseVO disconnect() {
        try {
            mqttService.disconnect();
            return MqttMessageResponseVO.success("MQTT连接已断开");
        } catch (Exception e) {
            log.error("断开MQTT连接异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("断开MQTT连接异常: " + e.getMessage());
        }
    }

    /**
     * 订阅MQTT主题
     */
    @PostMapping("/subscribe")
    @Operation(summary = "订阅MQTT主题", description = "订阅指定的MQTT主题")
    public MqttMessageResponseVO subscribe(
            @Parameter(description = "主题名称", required = true) @RequestParam String topic,
            @Parameter(description = "服务质量等级(0,1,2)") @RequestParam(defaultValue = "1") int qos) {
        try {
            boolean result = mqttService.subscribe(topic, qos);
            if (result) {
                return MqttMessageResponseVO.success("订阅主题成功: " + topic);
            } else {
                return MqttMessageResponseVO.failure("订阅主题失败: " + topic);
            }
        } catch (Exception e) {
            log.error("订阅MQTT主题异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("订阅MQTT主题异常: " + e.getMessage());
        }
    }

    /**
     * 取消订阅MQTT主题
     */
    @PostMapping("/unsubscribe")
    @Operation(summary = "取消订阅MQTT主题", description = "取消订阅指定的MQTT主题")
    public MqttMessageResponseVO unsubscribe(
            @Parameter(description = "主题名称", required = true) @RequestParam String topic) {
        try {
            mqttService.unsubscribe(topic);
            return MqttMessageResponseVO.success("取消订阅主题成功: " + topic);
        } catch (Exception e) {
            log.error("取消订阅MQTT主题异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("取消订阅MQTT主题异常: " + e.getMessage());
        }
    }

    /**
     * 发布MQTT消息
     */
    @PostMapping("/publish")
    @Operation(summary = "发布MQTT消息", description = "向指定主题发布消息")
    public MqttMessageResponseVO publish(
            @Parameter(description = "主题名称", required = true) @RequestParam String topic,
            @Parameter(description = "消息内容", required = true) @RequestParam String message,
            @Parameter(description = "服务质量等级(0,1,2)") @RequestParam(defaultValue = "1") int qos) {
        try {
            boolean result = mqttService.publish(topic, message, qos);
            if (result) {
                return MqttMessageResponseVO.success("发布消息成功");
            } else {
                return MqttMessageResponseVO.failure("发布消息失败");
            }
        } catch (Exception e) {
            log.error("发布MQTT消息异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("发布MQTT消息异常: " + e.getMessage());
        }
    }

    /**
     * 获取MQTT连接状态
     */
    @GetMapping("/status")
    @Operation(summary = "获取MQTT连接状态", description = "查询当前MQTT连接状态")
    public MqttMessageResponseVO getStatus() {
        try {
            boolean connected = mqttService.isConnected();
            return MqttMessageResponseVO.success(connected ? "已连接" : "未连接");
        } catch (Exception e) {
            log.error("获取MQTT连接状态异常: {}", e.getMessage(), e);
            return MqttMessageResponseVO.failure("获取MQTT连接状态异常: " + e.getMessage());
        }
    }

接口层


/**
 * MQTT消息接收服务接口
 * 基于G7易流MQTT推送服务
 */
public interface MqttService {

    /**
     * 连接MQTT服务器
     * @return 连接是否成功
     */
    boolean connect();

    /**
     * 断开MQTT连接
     */
    void disconnect();

    /**
     * 订阅主题
     * @param topic 主题名称
     * @param qos 服务质量等级
     * @return 订阅是否成功
     */
    boolean subscribe(String topic, int qos);

    /**
     * 取消订阅主题
     * @param topic 主题名称
     */
    void unsubscribe(String topic);

    /**
     * 发布消息
     * @param topic 主题名称
     * @param message 消息内容
     * @param qos 服务质量等级
     * @return 发布是否成功
     */
    boolean publish(String topic, String message, int qos);

    /**
     * 检查连接状态
     * @return 是否已连接
     */
    boolean isConnected();
}

如何实现共享消费(单实例单次消费)?

本地启动连接之后就会针对testtopic/1 topic有两个消费组,控制台发布一次消息之后,两个消费组都会消费消息。如何实现一个实例消费一次消息呢?就要使用mqtt的共享消费了($share/abc/t/1)。

image.png

image.png

实现以及结果

代码里我也是这样进行topic进行订阅,配置之后,就会轮询进行消费消息了。多次点击mqtt的控制台消息发布按钮就会发现,控制台的订阅者和我本地的订阅者共享订阅同一个topic的时候就会轮询进行消费了。

image.png

本地接口测试

也可以本地接口测试发送消息,具体可见controller 控制层的相关方法

image.png

image.png

五、总结

快速部署:使用 Docker 可一键部署 EMQX,无需复杂配置,适合快速开发测试。
核心配置:Java 客户端需关注MqttConnectOptions的自动重连、清理会话、QoS 等级配置,保证消息传输可靠性。
重复消费解决:采用 MQTT 共享订阅($share/消费组/主题),可实现同一消费组内轮询消费,避免重复处理。
生产注意:生产环境需配置 EMQX 集群、客户端认证加密、消息持久化,同时消费端需实现幂等性处理,进一步避免重复消费带来的业务异常。