微消息队列 MQTT 版是阿里云推出的一款面向移动互联网以及物联网领域的轻量级消息中间件。简单的来说,可以使用它来实现设备端和云端的通讯。快速实现效果。
一 阿里云微消息队列 MQTT
分享个阿里云2000元代金劵,免费领取可以给你购买服务器节省一些资金
购买后会创建一个实例,实例信息如下:
接下来要创建topic和group。
1.创建topic
MQTT 协议支持多级 Topic,父级 Topic 需在控制台创建,子级 Topic 无需创建。选择 Topic 管理 > 创建 Topic ,在创建 Topic 对话框,输入 Topic 以及描述,单击确认,可以在 Topic 管理页面查看刚创建的 Topic。
2.创建group
订阅消息的时候要用到group,选择 Group 管理 > 创建 Group ID,在创建 Group ID 对话框,输入 Group ID,然后单击确认,就可以可以在 Group ID 列表中查看到刚创建的 Group ID。
3.通过Java收发消息
(1).pom.xml
commons-codec
commons-codec
1.10
org.eclipse.paho
org.eclipse.paho.client.mqttv3
1.2.2
org.apache.httpcomponents
httpclient
4.5.2
com.alibaba
fastjson
1.2.48
com.aliyun
aliyun-java-sdk-onsmqtt
1.0.3
com.aliyun
aliyun-java-sdk-core
4.5.0
(2).java代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class MQ4IoTSendMessageToMQ4IoTUseSignatureMode {
public static void main(String[] args) throws Exception {
/**
* MQ4IOT 实例 ID,购买后控制台获取
*/
String instanceId = "post-cn-n6w*********";
/**
* 接入点地址,购买 MQ4IOT 实例,且配置完成后即可获取,接入点地址必须填写分配的域名,不得使用 IP 地址直接连接,否则可能会导致客户端异常。
*/
String endPoint = "post-cn-n6w********.mqtt.aliyuncs.com";
/**
* 账号 accesskey,从账号系统控制台获取
*/
String accessKey = "LTAIOZZg********";
/**
* 账号 secretKey,从账号系统控制台获取,仅在Signature鉴权模式下需要设置
*/
String secretKey = "v7CjUJCMk7j9aK****************";
/**
* MQ4IOT clientId,由业务系统分配,需要保证每个 tcp 连接都不一样,保证全局唯一,如果不同的客户端对象(tcp 连接)使用了相同的 clientId 会导致连接异常断开。
* clientId 由两部分组成,格式为 GroupID@@@DeviceId,其中 groupId 在 MQ4IOT 控制台申请,DeviceId 由业务方自己设置,clientId 总长度不得超过64个字符。
*/
String clientId = "GID_MQTT_Client1@@@device1";
/**
* MQ4IOT 消息的一级 topic,需要在控制台申请才能使用。
* 如果使用了没有申请或者没有被授权的 topic 会导致鉴权失败,服务端会断开客户端连接。
*/
final String parentTopic = "MQTT_Topic";
/**
* MQ4IOT支持子级 topic,用来做自定义的过滤,此处为示意,可以填写任何字符串,具体参考https://help.aliyun.com/document_detail/42420.html?spm=a2c4g.11186623.6.544.1ea529cfAO5zV3
* 需要注意的是,完整的 topic 长度不得超过128个字符。
*/
final String mq4IotTopic = parentTopic + "/" + "testMq4Iot";
/**
* QoS参数代表传输质量,可选0,1,2,根据实际需求合理设置,具体参考 https://help.aliyun.com/document_detail/42420.html?spm=a2c4g.11186623.6.544.1ea529cfAO5zV3
*/
final int qosLevel = 0;
ConnectionOptionWrapper connectionOptionWrapper = new ConnectionOptionWrapper(instanceId, accessKey, secretKey, clientId);
final MemoryPersistence memoryPersistence = new MemoryPersistence();
/**
* 客户端使用的协议和端口必须匹配,具体参考文档 https://help.aliyun.com/document_detail/44866.html?spm=a2c4g.11186623.6.552.25302386RcuYFB
* 如果是 SSL 加密则设置ssl://endpoint:8883
*/
final MqttClient mqttClient = new MqttClient("tcp://" + endPoint + ":1883", clientId, memoryPersistence);
/**
* 客户端设置好发送超时时间,防止无限阻塞
*/
mqttClient.setTimeToWait(5000);
final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
mqttClient.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
/**
* 客户端连接成功后就需要尽快订阅需要的 topic
*/
System.out.println("connect success");
executorService.submit(new Runnable() {
@Override
public void run() {
try {
final String topicFilter[] = {mq4IotTopic};
final int[] qos = {qosLevel};
mqttClient.subscribe(topicFilter, qos);
} catch (MqttException e) {
e.printStackTrace();
}
}
});
}
@Override
public void connectionLost(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
/**
* 消费消息的回调接口,需要确保该接口不抛异常,该接口运行返回即代表消息消费成功。
* 消费消息需要保证在规定时间内完成,如果消费耗时超过服务端约定的超时时间,对于可靠传输的模式,服务端可能会重试推送,业务需要做好幂等去重处理。超时时间约定参考限制
* https://help.aliyun.com/document_detail/63620.html?spm=a2c4g.11186623.6.546.229f1f6ago55Fj
*/
System.out.println(
"receive msg from topic " + s + " , body is " + new String(mqttMessage.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
System.out.println("send msg succeed topic is : " + iMqttDeliveryToken.getTopics()[0]);
}
});
mqttClient.connect(connectionOptionWrapper.getMqttConnectOptions());
for (int i = 0; i < 1; i++) {
MqttMessage message = new MqttMessage("hello mq4Iot pub sub msg".getBytes());
message.setQos(qosLevel);
/**
* 发送普通消息时,topic 必须和接收方订阅的 topic 一致,或者符合通配符匹配规则
*/
mqttClient.publish(mq4IotTopic, message);
/**
* MQ4IoT支持点对点消息,即如果发送方明确知道该消息只需要给特定的一个设备接收,且知道对端的 clientId,则可以直接发送点对点消息。
* 点对点消息不需要经过订阅关系匹配,可以简化订阅方的逻辑。点对点消息的 topic 格式规范是 {{parentTopic}}/p2p/{{targetClientId}}
*/
final String p2pSendTopic = parentTopic + "/p2p/" + clientId;
message = new MqttMessage("hello mq4Iot p2p msg".getBytes());
message.setQos(qosLevel);
mqttClient.publish(p2pSendTopic, message);
}
Thread.sleep(Long.MAX_VALUE);
}
}
上面那段代码也是阿里云官方提供的demo,我全部拿过来稍微改下,在本地和硬件那边调试,完全没有问题,一整欣喜,以为就这么简单,于是,把上面的代码融合到我的项目里面,核心代码如下:
@Service("mqService")
public class MqService {
@PostConstruct
public void recvMsg() throws InterruptedException {
mqttSubscribe();
}
private void mqttSubscribe(){
.............
public void messageArrived(String topic, MqttMessage message) throws Exception {
String payload=new String(message.getPayload());
HashMap map=(HashMap) JSON.parseObject(payload,HashMap.class);
String deviceId=map.get("deviceId");
String xxx=map.get("xxx");
..........................................
//开始做业务,比如操作数据库
}
}
}
项目发布到一台云服务器上,能正常收发,一切正常,后面又加了一台服务器做集群,这个时候问题来了,两台服务器都能同时订阅到设备端发上来的信息,这个时候同一个业务做了两遍,这个肯定不行的。重新查阅了相关资料,发现MQTT不建议云端业务直连,对mqtt服务器来说,云端业务通过mqtt协议接入的,也是一个设备。为了解决刚才的问题,微消息队列 MQTT 会将数据流出到MQ(阿里云用RocketMQ),然后业务后台对接MQ做业务。
二 阿里云RocketMQ
购买后需要创建实例,topic和group。
1.创建实例
2.创建topic
3.创建group
三 MQTT创建数据流出规则
1.Java通过MQ,获取设备上报信息
(1).pom.xml
com.aliyun.openservices
ons-client
1.8.7.1.Final
(2).Java代码
import com.aliyun.openservices.ons.api.Action;
import com.aliyun.openservices.ons.api.ConsumeContext;
import com.aliyun.openservices.ons.api.Consumer;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.MessageListener;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import java.util.Properties;
public class ConsumerTest {
public static void main(String[] args) {
Properties properties = new Properties();
// 您在控制台创建的 Group ID。
properties.put(PropertyKeyConst.GROUP_ID, "GID_MessageConsumer");
// AccessKey ID 阿里云身份验证,在阿里云 RAM 控制台创建。
properties.put(PropertyKeyConst.AccessKey, "LTAIOZZg********");
// Accesskey Secret 阿里云身份验证,在阿里云服 RAM 控制台创建。
properties.put(PropertyKeyConst.SecretKey, "v7CjUJCMk7j9aK****************");
// 设置 TCP 接入域名,进入控制台的实例详情页面的 TCP 协议客户端接入点区域查看。
properties.put(PropertyKeyConst.NAMESRV_ADDR, "http://MQ_INST_***************_BcLPQ2p0.mq-internet-access.mq-internet.aliyuncs.com:80");
// 集群订阅方式 (默认)。
// properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);
// 广播订阅方式。
// properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.BROADCASTING);
Consumer consumer = ONSFactory.createConsumer(properties);
//订阅设备上行消息
consumer.subscribe("MessageFromMQTT", "*", new MessageListener() { //订阅多个 Tag。
public Action consume(Message message, ConsumeContext context) {
System.out.println("Receive: " + message);
return Action.CommitMessage;
}
});
consumer.start();
System.out.println("Consumer Started");
}
}
上面那段代码也是阿里云官方提供的demo,我全部拿过来稍微改下,在本地和硬件那边调试,能正常获取设备端上报的信息,于是,把上面的代码融合到我的项目里面,核心代码如下:
@Service("mqService")
public class MqService {
@PostConstruct
public void recvMsg() throws InterruptedException {
mqSubscribe();
}
private void mqSubscribe(){
consumer.subscribe("topic-test", "*", new MessageListener() { //订阅多个 Tag。
public Action consume(Message message, ConsumeContext context) {
String payload=new String(message.getBody());
........
//开始做业务,操作数据库
}
});
}
}
项目发布到两台云服务器上,集群模式下,同一个设备上报信息只能被一台服务器订阅,一切正常,这个就是我们要的效果。
四 MQTT创建数据流入规则
通过云端后台发消息给设备端,也是通过MQ流转到MQTT。
1.Java通过MQ,给设备端发送信息
(1).pom.xml
com.aliyun.openservices
ons-client
1.8.7.1.Final
(2).Java代码
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.SendResult;
import com.aliyun.openservices.ons.api.ONSFactory;
import com.aliyun.openservices.ons.api.PropertyKeyConst;
import java.util.Date;import java.util.Properties;
public class SendMQMessageToMQTT {
public static void main(String[] args) {
Properties properties = new Properties();
// AccessKeyId 阿里云身份验证,在阿里云用户信息管理控制台获取。
properties.put(PropertyKeyConst.AccessKey,"LTAIOZZg**********");
// AccessKeySecret 阿里云身份验证,在阿里云用户信息管理控制台获取。
properties.put(PropertyKeyConst.SecretKey, "v7CjUJCMk7j9aK****************");
//设置发送超时时间,单位毫秒。
properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");
// 设置 TCP 接入域名,进入控制台的实例详情页面的 TCP 协议客户端接入点区域查看。
properties.put(PropertyKeyConst.NAMESRV_ADDR, "http://MQ_INST_********_BcLPQ2p0.mq-internet-access.mq-internet.aliyuncs.com:80");
Producer producer = ONSFactory.createProducer(properties);
// mqttSecondTopic:https://help.aliyun.com/document_detail/112971.html?spm=a2c4g.11186623.6.579.403242ca4pOcpC
properties.put("mqttSecondTopic","testMq4Iot");
// 在发送消息前,必须调用 start 方法来启动 Producer,只需调用一次即可。
producer.start();
//循环发送消息。
for (int i = 0; i < 1; i++){
Message msg = new Message("MessageToMQTT","","MQ Message To MQTT".getBytes());
msg.setKey("ORDERID_" + i);
msg.setUserProperties(properties);
try {
SendResult sendResult = producer.send(msg);
// 同步发送消息,只要不抛异常就是成功。
if (sendResult != null) {
System.out.println(new Date() + " Send mq message success. Topic is:" + msg.getTopic() + " msgId is: " + sendResult.getMessageId());
}
}
catch (Exception e) {
// 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理。
System.out.println(new Date() + " Send mq message failed. Topic is:" + msg.getTopic());
e.printStackTrace();
}
}
// 在应用退出前,销毁 Producer 对象。
// 注意:如果不销毁也没有问题。
producer.shutdown();
}
}
2.Java通过MQTT直接发消息给设备
其实也可以直接通过MQTT云端API直接发消息给设备,端到端,设备到设备,当然消息的统一收集阿里云官方建议还是用mq做为中转。
(1).pom.xml
com.aliyun
aliyun-java-sdk-core
4.5.6
com.aliyun
aliyun-java-sdk-onsmqtt
1.0.4
(2).Java代码
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.google.gson.Gson;
import com.aliyuncs.onsmqtt.model.v20200420.*;
public class SendMessage {
public static void main(String[] args) {
DefaultProfile profile = DefaultProfile.getProfile("mq-internet-access", "LTAIOZZg********", "v7CjUJCMk7j9aK****************");
IAcsClient client = new DefaultAcsClient(profile);
SendMessageRequest request = new SendMessageRequest();
request.setRegionId("mq-internet-access");
request.setInstanceId("post-cn-n6w********");
request.setPayload("message from manager api!");
request.setMqttTopic("MQTT_Topic/testMq4Iot");
try {
SendMessageResponse response = client.getAcsResponse(request);
System.out.println(new Gson().toJson(response));
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
System.out.println("ErrCode:" + e.getErrCode());
System.out.println("ErrMsg:" + e.getErrMsg());
System.out.println("RequestId:" + e.getRequestId());
}
}
}
上面那段代码也是阿里云官方提供的demo,我全部拿过来稍微改下,在本地和硬件那边调试,发送消息给设备端,设备端能订阅到。