mqtt 发送消息过多_阿里云MQTT服务端注解式消息处理分发与同步调用实践小结

本文介绍了在服务端使用RocketMQ对接阿里云MQTT,实现MQTT消息订阅与发布,以及注解式消息处理分发。通过自定义注解、BeanPostProcessor和ApplicationListener,实现在MQTT消息处理中的分发。同时,通过一套请求和响应的同步机制,解决了MQTT异步调用的问题,详细展示了同步调用的实现思路和细节。
摘要由CSDN通过智能技术生成

一、前言

前段时间公司预研了设备app端与服务端的交互方案,出于多方面考量最终选用了阿里云的微服务队列MQTT方案,基于此方案,本人主要实践有:

1. 封装了RocketMQ实现MQTT订阅与发布的实现细节;

2. 实现了注解式分发处理,可利用如MqttController, MqttTopicMapping等相关自定义注解的方式来统一订阅MQTT的Topic以及消息处理的分发;

3. 使用了一套请求和响应的同步机制来达到PUB/SUB异步通信的伪同步调用。

Github 地址点此链接

二、RocketMQ的接入细节

1. 为什么服务端要使用RocketMQ接入

阿里云微消息队列MQTT是在以消息队列 RocketMQ 为核心存储的基础上,实现更适合移动互联网和IoT领域的无状态网关,两者之间具备天然的数据互通性。MQTT实例本身并不提供消息数据持久化功能,消息数据持久化需要搭配后端的消息存储实例来使用。因此现阶段每一个阿里云MQTT实例都必须配套一个消息存储实例,即RocketMQ实例来提供消息数据持久化功能,因此他们之间可以说是消息互通的,即可用RocketMQ订阅的方式来消费用MQTT协议发布的消息,同理也可用 MQTT协议订阅的方式来消费RocketMQ发布的消息。

帮助文档也给出了以下两种产品的区别说明:

微消息队列MQTT基于MQTT协议实现,单个客户端的处理能力较弱。因此,微消息队列MQTT适用于拥有大量在线客户端(很多企业设备端过万,甚至上百万),但每个客户端消息较少的场景。
相比之下,消息队列RocketMQ是面向服务端的消息引擎,主要用于服务组件之间的解耦、异步通知、削峰填谷等,服务器规模较小(极少企业服务器规模过万),但需要大量的消息处理,吞吐量要求高。因此,消息队列RocketMQ适用于服务端进行大批量的数据处理和分析的场景。

基于以上区别,官方也推荐在移动端设备上使用微消息队列MQTT,而在服务端应用中则使用消息队列RocketMQ,具体则可以通过 MQTT SDK 以公网访问方式来实现设备间的通信,通过MQ SDK以内网方式来实现服务端通信。

2. RocketMQ如何对接

RocketMQ与MQTT在消息结构和一些属性字段上都有一定的映射关系,具体内容(摘自帮助文档)如下。

微消息队列MQTT使用MQTT协议接入,而消息队列RocketMQ使用的是私有协议,因此,两者的关键概念存在如下映射关系。

fb73025e85d4341240c5a9c1b93aec9e.png
MQ与MQTT消息结构映射关系

如上图所示,MQTT协议中Topic是多级结构,而消息队列RocketMQ的Topic 仅有一级,因此,MQTT中的一级Topic映射到消息队列RocketMQ的Topic,而二级和三级Topic则映射到消息队列RocketMQ的消息属性(Properties)中。

消息队列 RocketMQ 协议中的消息(Message)可以拥有自定义属性(Properties),而MQTT协议目前的版本不支持属性,但为了方便对MQTT协议中的Header信息和设备信息进行溯源,MQTT的部分信息将被映射到 RocketMQ的消息属性中,方便使用消息队列RocketMQ的SDK接入的用户获取。

目前,微消息队列MQTT和消息队列RocketMQ支持的属性字段映射表如下图所示。使用消息队列RocketMQ的SDK的应用和使用消息队列MQTT的SDK的应用进行交互时,可以通过读写这些属性字段来达到信息获取或者设置的目的。

6562f3a1a63d4e993ef95de3ce2290b9.png
属性字段映射关系

3. RocketMQ对MQTT消息订阅的实现

Properties properties = new Properties();
// 在控制台创建的Group ID
properties.put(PropertyKeyConst.GROUP_ID, "xxx");
// 阿里云AccessKey 
properties.put(PropertyKeyConst.AccessKey, "xxx");
// 阿里云SecretKey
properties.put(PropertyKeyConst.SecretKey, "xxx");
// 在RocketMQ控制台的实例基本信息中可查看到的TCP协议接入点
properties.put(PropertyKeyConst.NAMESRV_ADDR,
        "xxx");
Consumer consumer = ONSFactory.createConsumer(properties);
consumer.subscribe("topic", "*", new MessageListener() {
     //订阅全部 Tag
            public Action consume(Message message, ConsumeContext context) {
    
                //获得mqtt消息中的第一级topic
                String mqttFirstTopic = message.getTopic();
                //获得mqtt消息中除去1级后的所有topic
                String mqttSecondTopic = message.getUserProperties(PropertyKeyConst.MqttSecondTopic);
                //获得mqtt消息中的messageId
                String messageId = message.getUserProperties("UNIQ_KEY");
                //获得mqtt消息中的消息体
                String messageBody = new String(message.getBody());
                //...
                return Action.CommitMessage;
            }
        });
consumer.start();

实现主要注意2点:

  • 这边的 MQ 只需要订阅 MQTT 的一级 Topic 。如果 MQTT 会发布2个 Topic 的消息 robot/alarmrobot/task/test ,则在此处只需要订阅 robot 这个第一级Topic即可。
  • MQTT 的一些属性字段可以从 RocketMQ 消息 MessageuserProperties 字段中获得,比如上面代码中通过 message.getUserProperties(PropertyKeyConst.MqttSecondTopic); 可以获得 MQTT 中的 除去1级后的所有 Topic 字符串,如上述举例的2个 Topic 可分别获得 /alarm/task/test。 具体能够获得哪些字段可以参考上一节的属性字段映射表,也可自行查看 PropertyKeyConst 类中定义的一些字符串常量来大概知晓。

使用阿里云MQTT控制台发送一个MQTT消息,如图所示:

f451754dda09e418f7b4d64a925c7019.png
MQTT控制台发送消

在程序中加一个断点获得当前Message对象的字段如下:

1c4d74b33f6cdf96c40039d87619bf90.png
Message消息体

上图可看到userProperties中的一些值,比如qoslevelmqttSecondTopic等,这些字段都可以在PropertyKeyConst 类中找到对应的字符串常量,但是UNIQ_KEYcleansessionflagPropertyKeyConst 类中并没有对应的字符串常量,这边暂时就message.getUserProperties("UNIQ_KEY")这样使用自定义字符量来获得。

4. RocketMQ对MQTT消息发布的实现

Properties properties = new Properties();
// 在控制台创建的Group ID
properties.put(PropertyKeyConst.GROUP_ID, "xxx");
// 阿里云AccessKey 
properties.put(PropertyKeyConst.AccessKey, "xxx");
// 阿里云SecretKey
properties.put(PropertyKeyConst.SecretKey, "xxx");
// 在RocketMQ控制台的实例基本信息中可查看到的TCP协议接入点
properties.put(PropertyKeyConst.NAMESRV_ADDR,
        "xxx");
//设置发送超时时间,单位毫秒
properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
Producer producer = ONSFactory.createProducer(properties);
// 在发送消息前,必须调用 start 方法来启动 Producer,只需调用一次即可
producer.start();


//发送一个mqtt消息
String parentTopic = topic.substring(0, topic.indexOf("/"));
String subTopic = topic.substring(topic.indexOf("/"));
Message msg = new Message(parentTopic, "", message.getBytes());
msg.putUserProperties(PropertyKeyConst.MqttSecondTopic, subTopic);
msg.putUserProperties(PropertyKeyConst.MqttQOS, qos);
msg.putUserProperties("cleansessionflag", "" + cleanSessionFlag);
SendResult result = producer.send(msg);
  • 该代码仅实现了普通消息的同步发送,若需发送顺序消息、延时消息等,可参考SDK帮助文档创建不同的Producer实现即可。
  • 上述代码将需要发送的MQTT全量Topic拆分成1级与2级,1级Topic设置为MQ中的Topic参数,2级Topic字符串则设为userPropertiesPropertyKeyConst.MqttSecondTopic的,其他属相如qoslevelcleansessionflag等也是通过userProperties的相关字段来设置。

三、注解式分发处理的实现

1. 前置知识点

1.1 BeanPostProcessor

BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口。BeanPostProcessor接口定义了两个方法:

public interface BeanPostProcessor {
    
    // 前置处理
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // 后置处理
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Spring中Bean的整个生命周期如图所示:

992704fcfcdeba2ece274adf068d09a5.png
Bean生命周期

postProcessBeforeInitialization()方法与postProcessAfterInitialization()分别对应图中前置处理和后置处理两个步骤将执行的方法。这两个方法中都传入了bean对象实例的引用,为扩展容器的对象实例化过程提供了很大便利,在这儿几乎可以对传入的实例执行任何操作。

可以看到,Spring容器通过BeanPostProcessor给了我们一个机会对Spring管理的bean进行再加工,注解、AOP等功能的实现均大量使用了BeanPostProcessor。通过实现BeanPostProcessor的接口,在其中处理方法中判断bean对象上是否有自定义的一些注解,如果有,则可以对这个bean实例继续进行其他操作,这也是本例中使用该接口要实现的主要目的。

1.2 ApplicationListener

在IOC的容器的启动过程,当所有的bean都已经处理完成之后,spring ioc容器会有一个发布事件的动作。从 AbstractApplicationContext 的源码中就可以看出:

protected void finishRefresh() {
    
    // Initialize lifecycle processor for 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值