MQTT生产实践总结与EMQX5.0探索
引言
之前做了一个系统,系统的一部分硬件设备是使用MQTT协议接入的,无奈只好去研究它。好不容拼凑出一套能用的代码,上生产以后频频出现故障,后来稀里糊涂的解决了,但是一直没有弄明白原因。这次发现EMQX出了5.0的版本研究了一下,看文档的时候也突然明白了之前出问题的原因,因此做一下记录和总结。
本文所有的代码都是基于开源项目fusesource/mqtt-client,EMQX官方给的java代码实在是太丑了。fusesource/mqtt-client项目的地址如下:https://github.com/fusesource/mqtt-client
关于EMQX
EMQX是一款非常优秀的MQTT消息服务器,几乎适用于所有主流的操作系统而且文档非常详细丰富,但是就是有些例子不够清楚,需要实践才能搞明白。
EMQX文档地址:https://www.emqx.io/docs/zh/v5.0/
因为EMQX的文档已经很详细了,所以我只把我遇到的问题简单说明一下。为什么要说一下授权,因为这里涉及到了MQTT配置的通配符,而官网文档没有说明,我自己花了很长时间实验出来的。通配符有两种分别是#和+。举个例子
比如mqtt/# 则可以匹配所有以mqtt/开头的话题 例如mqtt/hq/12345/c 、mqtt/d1/12345/c 而+只能匹配一层 如/mqtt/hq/+/c 只能匹配mqtt/hq/12345/c 而不能匹配 mqtt/d1/12345/c。为什么要讲通配符因为在实际使用中为了区别每台设备,经常会使用设备编号进行区分这样也会有很多像mqtt/hq/12345/c、mqtt/hq/12346/c 这种主题的订阅服务器端不可能每台都订阅一次所以就会使用通配符订阅如 mqtt/hq/+/c 就可以一次订阅所有设备的话题。
另外因为设备很多也都在外面(非安全区域)有心人可以获取到的账号信息,甚至可以通过订阅你的话题窃取信息,如果你是用的是共享订阅那可能导致你自己的服务器订阅会丢失一部分数据。所以针对用客户端账号要严格限制 使用 mqtt/# 配置客户端账号匹配所有话题禁止客户端订阅关键话题。
最后总结EMQX的授权机制:如果订阅的时候没有订阅权限会直接提示没有权限,请检查配置。推送的时候 如果没有推送权限则不会提示 只会把这条数据当作垃圾数据丢弃了(MQTTX 工具测试)。
fusesource/mqtt-client和EMQX的坑
先说一下fusesource/mqtt-client的机制,fusesource/mqtt-client中订阅端的状态必须和推送的客户端是一个状态换句话说只能用一个实例来创建订阅和推送(否则订阅不到消息,也推送不了消息)。所以很显然我这里是用了单例子模式来创建mqtt的客户端。而EMQX有一个过载保护机制,意思就是消息数量或消息字节到一定的量就会主动切断客户端的连接。诸位想想单例模式中正在连接的客户端被服务端主动断开连接,还能在发送数据吗?很明显不能。之前我在生产中遇到的就是遇到这个问题。困扰我好几个月,不过那时候我并不知道EMQX有消息过载保护,单纯的以为数量过大压力导致的,所以我打算用消息共享订阅来解决,为了使用共享订阅,我把所有的消息插入到数据库然后轮询推送数据,解决了这个问题。所以很长时间我一直都以为我是因为是用共享订阅解决的这个问题。看到这个过载保护我才知道,是因为我把数据插入到数据库然后平缓推送解决了这个问题。
EMQX机制
EMQX和常规的队列不同,不能持久化。简单实践了一下:如果客户端在线但是订阅端不在线,EMQX服务端会直接把所有数据都丢掉。如果客户端和订阅端都在线,客户端发送数据即使订阅端处理数据很慢也能收到所有数据。
实践代码
这个是我从项目中抽出来单独做的一个demo,是经历过实际生产的一般的项目应该可以直接使用。代码我就懒的贴出来了,下面放了一个github的一个项目地址。
序号 | 环境 | 版本 |
---|---|---|
1 | jdk | 17 |
2 | org.springframework.boot | 2.7.5 |
3 | fusesource/mqtt-client | 1.16 |
4 | gradle | 7.5.1 |
代码地址:https://github.com/duzhaosongyue/mqtt_server
我的实践都是根据当前最新的版本实践的,如果切换到jdk8可能需要修改一些代码,具体可以参考:https://github.com/fusesource/mqtt-client