1. 前言

公司的IOT平台主要采用MQTT(消息队列遥测传输)对底层的驱动做命令下发和数据采集。也用到了redis、zeroMQ、nats等消息中间件。今天先整理SpringBoot集成MQTT笔记和工作中遇到的问题。

2. MQTT介绍

MQTT is a machine-to-machine (M2M)/"Internet of Things" connectivity protocol. It was designed as an extremely lightweight publish/subscribe messaging transport. It is useful for connections with remote locations where a small code footprint is required and/or network bandwidth is at a premium.

官网地址:http://mqtt.org/https://www.mqtt.com/

MQTT除了具备大部分消息中间件拥有的功能外,其最大的特点就是小型传输。以减少开销,减低网络流量的方式去满足低带宽、不稳定的网络远程传输。

MQTT服务器有很多,比如Apache-Apollo和EMQX,ITDragon龙 目前使用的时EMQX作为MQTT的服务器。使用也很简单,下载解压后,进入bin目录执行emqx console 启动服务。

MQTT调试工具可以用MQTTBox

3. SpringBoot 集成MQTT

3.1 导入mqtt库

第一步:导入面向企业应用集成库和对应mqtt集成库

  1. compile('org.springframework.boot:spring-boot-starter-integration')
  2. compile('org.springframework.integration:spring-integration-mqtt')

这里要注意spring-integration-mqtt的版本。因为会存在org.eclipse.paho.client.mqttv3修复了一些bug,并迭代了新版本。但spring-integration-mqtt并没有及时更新的情况。修改方法如下

  1. compile("org.springframework.integration:spring-integration-mqtt") {
  2. exclude group: "org.eclipse.paho" , module: "org.eclipse.paho.client.mqttv3"
  3. }
  4. compile("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2")

第二步:MQTT连接配置文件

  1. # MQTT Config
  2. mqtt.server=tcp://x.x.x.x:1883
  3. mqtt.username=xxx
  4. mqtt.password=xxx
  5. mqtt.client-id=clientID
  6. mqtt.cache-number=100
  7. mqtt.message.topic=itDragon/tags/cov

3.2 配置MQTT订阅者

Inbound 入站消息适配器

第一步:配置MQTT客户端工厂类DefaultMqttPahoClientFactory

第二步:配置MQTT入站消息适配器MqttPahoMessageDrivenChannelAdapter

第三步:定义MQTT入站消息通道MessageChannel

第四步:声明MQTT入站消息处理器MessageHandler

以下有些配置是冲突或者重复的,主要是体现一些重要配置。

  1. package com.itdragon.server.config
  2. import com.itdragon.server.message.ITDragonMQTTMessageHandler
  3. import org.eclipse.paho.client.mqttv3.MqttConnectOptions
  4. import org.springframework.beans.factory.annotation.Value
  5. import org.springframework.context.annotation.Bean
  6. import org.springframework.context.annotation.Configuration
  7. import org.springframework.integration.annotation.ServiceActivator
  8. import org.springframework.integration.channel.DirectChannel
  9. import org.springframework.integration.core.MessageProducer
  10. import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory
  11. import org.springframework.integration.mqtt.core.MqttPahoClientFactory
  12. import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter
  13. import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter
  14. import org.springframework.messaging.MessageChannel
  15. import org.springframework.messaging.MessageHandler
  16. import java.time.Instant
  17. @Configuration
  18. class MQTTConfig {
  19. @Value("\${mqtt.server}")
  20. lateinit var mqttServer: String
  21. @Value("\${mqtt.user-name}")
  22. lateinit var mqttUserName: String
  23. @Value("\${mqtt.password}")
  24. lateinit var mqttUserPassword: String
  25. @Value("\${mqtt.client-id}")
  26. lateinit var clientID: String
  27. @Value("\${mqtt.cache-number}")
  28. lateinit var maxMessageInFlight: String
  29. @Value("\${mqtt.message.topic}")
  30. lateinit var messageTopic: String
  31. /**
  32. * 配置DefaultMqttPahoClientFactory
  33. * 1. 配置基本的链接信息
  34. * 2. 配置maxInflight,在mqtt消息量比较大的情况下将值设大
  35. */
  36. fun mqttClientFactory(): MqttPahoClientFactory {
  37. val mqttConnectOptions = MqttConnectOptions()
  38. // 配置mqtt服务端地址,登录账号和密码
  39. mqttConnectOptions.serverURIs = arrayOf(mqttServer)
  40. mqttConnectOptions.userName = mqttUserName
  41. mqttConnectOptions.password = mqttUserPassword.toCharArray()
  42. // 配置最大不确定接收消息数量,默认值10,qos!=0 时生效
  43. mqttConnectOptions.maxInflight = maxMessageInFlight.toInt()
  44. val factory = DefaultMqttPahoClientFactory()
  45. factory.connectionOptions = mqttConnectOptions
  46. return factory
  47. }
  48. /**
  49. * 配置Inbound入站,消费者基本连接配置
  50. * 1. 通过DefaultMqttPahoClientFactory 初始化入站通道适配器
  51. * 2. 配置超时时长,默认30000毫秒
  52. * 3. 配置Paho消息转换器
  53. * 4. 配置发送数据的服务质量 0~2
  54. * 5. 配置订阅通道
  55. */
  56. @Bean
  57. fun itDragonMqttInbound(): MessageProducer {
  58. // 初始化入站通道适配器,使用的是Eclipse Paho MQTT客户端库
  59. val adapter = MqttPahoMessageDrivenChannelAdapter(clientID + Instant.now().toEpochMilli(), mqttClientFactory(), messageTopic)
  60. // 设置连接超时时长(默认30000毫秒)
  61. adapter.setCompletionTimeout(30000)
  62. // 配置默认Paho消息转换器(qos=0, retain=false, charset=UTF-8)
  63. adapter.setConverter(DefaultPahoMessageConverter())
  64. // 设置服务质量
  65. // 0 最多一次,数据可能丢失;
  66. // 1 至少一次,数据可能重复;
  67. // 2 只有一次,有且只有一次;最耗性能
  68. adapter.setQos(0)
  69. // 设置订阅通道
  70. adapter.outputChannel = itDragonMqttInputChannel()
  71. return adapter
  72. }
  73. /**
  74. * 配置Inbound入站,消费者订阅的消息通道
  75. */
  76. @Bean
  77. fun itDragonMqttInputChannel(): MessageChannel {
  78. return DirectChannel()
  79. }
  80. /**
  81. * 配置Inbound入站,消费者的消息处理器
  82. * 1. 使用@ServiceActivator注解,表明所修饰的方法用于消息处理
  83. * 2. 使用inputChannel值,表明从指定通道中取值
  84. * 3. 利用函数式编程的思路,解耦MessageHandler的业务逻辑
  85. */
  86. @Bean
  87. @ServiceActivator(inputChannel = "itDragonMqttInputChannel")
  88. fun commandDataHandler(): MessageHandler {
  89. /*return MessageHandler { message ->
  90. println(message.payload)
  91. }*/
  92. return ITDragonMQTTMessageHandler()
  93. }
  94. }

注意:

  • 1)MQTT的客户端ID要唯一。
  • 2)MQTT在消息量大的情况下会出现消息丢失的情况。
  • 3)MessageHandler注意解耦问题。

3.3 配置MQTT发布者

Outbound 出站消息适配器

第一步:配置Outbound出站,出站通道适配器

第二步:配置Outbound出站,发布者发送的消息通道

第三步:对外提供推送消息的接口

在原有的MQTTConfig配置类的集成上补充以下内容

  1. /**
  2. * 配置Outbound出站,出站通道适配器
  3. * 1. 通过MqttPahoMessageHandler 初始化出站通道适配器
  4. * 2. 配置异步发送
  5. * 3. 配置默认的服务质量
  6. */
  7. @Bean
  8. @ServiceActivator(inputChannel = "itDragonMqttOutputChannel")
  9. fun itDragonMqttOutbound(): MqttPahoMessageHandler {
  10. // 初始化出站通道适配器,使用的是Eclipse Paho MQTT客户端库
  11. val messageHandler = MqttPahoMessageHandler(clientID + Instant.now().toEpochMilli() + "_set", mqttClientFactory())
  12. // 设置异步发送,默认是false(发送时阻塞)
  13. messageHandler.setAsync(true)
  14. // 设置默认的服务质量
  15. messageHandler.setDefaultQos(0)
  16. return messageHandler
  17. }
  18. /**
  19. * 配置Outbound出站,发布者发送的消息通道
  20. */
  21. @Bean
  22. fun itDragonMqttOutputChannel(): MessageChannel {
  23. return DirectChannel()
  24. }
  25. /**
  26. * 对外提供推送消息的接口
  27. * 1. 使用@MessagingGateway注解,配置MQTTMessageGateway消息推送接口
  28. * 2. 使用defaultRequestChannel值,调用时将向其发送消息的默认通道
  29. * 3. 配置灵活的topic主题
  30. */
  31. @MessagingGateway(defaultRequestChannel = "itDragonMqttOutputChannel")
  32. interface MQTTMessageGateway {
  33. fun sendToMqtt(data: String, @Header(MqttHeaders.TOPIC) topic: String)
  34. fun sendToMqtt(data: String, @Header(MqttHeaders.QOS) qos: Int, @Header(MqttHeaders.TOPIC) topic: String)
  35. }

注意:

  • 1)发布者和订阅者的客户端ID不能相同。
  • 2)消息的推送建议采用异步的方式。
  • 3)消息的推送方法可以只传payload消息体,但需要配置setDefaultTopic。

3.4 MQTT消息处理和发送

3.4.1 消息处理

为了让消息处理函数和MQTT配置解耦,这里提供MessageHandler 注册类,将消息处理的业务逻辑以函数式编程的思维注册到Handler中。

  1. package com.itdragon.server.message
  2. import org.springframework.messaging.Message
  3. import org.springframework.messaging.MessageHandler
  4. class ITDragonMQTTMessageHandler : MessageHandler {
  5. private var handler: ((String) -> Unit)? = null
  6. fun registerHandler(handler: (String) -> Unit) {
  7. this.handler = handler
  8. }
  9. override fun handleMessage(message: Message<*>) {
  10. handler?.run { this.invoke(message.payload.toString()) }
  11. }
  12. }

注册MessageHandler

  1. package com.itdragon.server.message
  2. import org.slf4j.LoggerFactory
  3. import org.springframework.beans.factory.annotation.Autowired
  4. import org.springframework.stereotype.Service
  5. import javax.annotation.PostConstruct
  6. @Service
  7. class ITDragonMessageDispatcher {
  8. private val logger = LoggerFactory.getLogger(ITDragonMessageDispatcher::class.java)
  9. @Autowired
  10. lateinit var itDragonMQTTMessageHandler: ITDragonMQTTMessageHandler
  11. @PostConstruct
  12. fun init() {
  13. itDragonMQTTMessageHandler.registerHandler { itDragonMsgHandler(it) }
  14. }
  15. fun itDragonMsgHandler(message: String) {
  16. logger.info("itdragon mqtt receive message: $message")
  17. try {
  18. // todo
  19. }catch (ex: Exception) {
  20. ex.printStackTrace()
  21. }
  22. }
  23. }

3.4.1 消息发送

注入MQTT的MessageGateway,然后推送消息。

  1. @Autowired
  2. lateinit var mqttGateway: MQTTConfig.MQTTMessageGateway
  3. @Scheduled(fixedDelay = 10*1000)
  4. fun sendMessage() {
  5. mqttGateway.sendToMqtt("Hello ITDragon ${Instant.now()}", "itDragon/tags/cov/set")
  6. }

4. 开发常见问题

4.1 MQTT每次重连失败都会增长线程数

项目上线一段时间后,客户的服务器严重卡顿。原因是客户服务断网后,MQTT在每次尝试重连的过程中一直在创建新的线程,导致一个Java服务创建了上万个线程。解决方案是更新了org.eclipse.paho.client.mqttv3的版本,也是 "3.1 导入mqtt库" 中提到的。后续就没有出现这个问题了。

4.2 MQTT消息量大存在消息丢失的情况

MQTT的消息量大的情况下,既要保障数据的完整,又要保障性能的稳定。光从MQTT本身上来说,很难做到鱼和熊掌不可兼得。ITDragon龙 先要理清需求:

  • 1)数据的完整性,主要用于能耗的统计、报警的分析
  • 2)性能的稳定性,服务器不挂

    SpringBoot 集成MQTT配置的更多相关文章

    1. SpringBoot集成Mybatis配置动态数据源

      很多人在项目里边都会用到多个数据源,下面记录一次SpringBoot集成Mybatis配置多数据源的过程. pom.xml <?xml version="1.0" encod ...

    2. 物联网架构成长之路(32)-SpringBoot集成MQTT客户端

      一.前言 这里虽然是说MQTT客户端.其实对于服务器来说,这里的一个具有超级权限的MQTT客户端,就可以做很多事情.比如手机APP或者网页或者第三方服务需要发送数据到设备,但是这些又不是设备,又不能让 ...

    3. SpringBoot集成mybatis配置

      一个有趣的现象:传统企业大都喜欢使用hibernate,互联网行业通常使用mybatis:之所以出现这个问题感觉与对应的业务有关,比方说,互联网的业务更加的复杂,更加需要进行灵活性的处理,所以myba ...

    4. springboot集成freemarker 配置application.properties详解

      #配置freemarker详解 #spring.freemarker.allow-request-override=false # Set whether HttpServletRequest att ...

    5. Spring Boot 集成 MQTT

      本文代码有些许问题,处理方案见:解决 spring-integration-mqtt 频繁报 Lost connection 错误 一.添加配置 spring: mqtt: client: usern ...

    6. springboot集成activiti6.0多数据源的配置

      最近公司开始开发springboot的项目,需要对工作流进行集成.目前activiti已经发布了7.0的版本,但是考虑到6.0版本还是比较新而且稳定的,决定还是选择activiti6.0的版本进行集成 ...

    7. SpringBoot系列之集成Druid配置数据源监控

      SpringBoot系列之集成Druid配置数据源监控 继上一篇博客SpringBoot系列之JDBC数据访问之后,本博客再介绍数据库连接池框架Druid的使用 实验环境准备: Maven Intel ...

    8. SpringBoot集成MyBatis的Bean配置方式

      SpringBoot集成MyBatis的Bean配置方式 SpringBoot是一款轻量级开发的框架,简化了很多原先的xml文件配置方式,接下来就介绍一下如何不适用XML来配置Mybatis spri ...

    9. SpringBoot集成Swagger2并配置多个包路径扫描

      1. 简介   随着现在主流的前后端分离模式开发越来越成熟,接口文档的编写和规范是一件非常重要的事.简单的项目来说,对应的controller在一个包路径下,因此在Swagger配置参数时只需要配置一 ...

    随机推荐

    1. iPhoneSE2要在印度独家生产真得没戏?

      现在,关于iPhone SE2的消息层出不穷,总的来说,它是一款真实存在的手机,整体性能和iPhone5X/SE相似,大概可能差不多会加上一些"无线充电"之类的无聊功能.普通消费者 ...

    2. [rope大法好] STL里面的可持久化平衡树--rope

      简单用法: #include <ext/rope> using namespace __gnu_cxx; int a[1000]; rope<int> x; rope<i ...

    3. 修改 commit message

      本文为原创文章,转载请标明出处 目录 修改上一条提交的 commit message 修改之前提交的 commit message 1. 修改上一条提交的 commit message git com ...

    4. IDEA如何自动添加注解作者等信息?

      1.点击File 2.点击Settings 3.点击Editor 4.点Live  Templates 5.点击左上角加号选中第2个 6.自定义命名,选中你自己创建的组,点击左上角加号选择第1个选项 ...

    5. 使用apache mail发送邮件错误解决办法

      今天在写发送邮件的程序时发现了以下两个些错误,贴出来跟大家分享分享 希望对大家有帮助. 错误一: Exception in thread "main" java.lang.NoCl ...

    6. ES插件升级

      #!/bin/bash mkdir -p /home/esuser cd /home/esuser wget http://10.12.xx.xx:8090/search_plugins/sd_wai ...

    7. swagger使用以及一些注解说明

      @Api:作用于Conntroller类上 value:字段说明 description:描述 tags:分组 (经常用到tags,例如如下,我只是给value,则默认应用了类名) @ApiOpera ...

    8. 输入URL到浏览器显示页面的过程,搜集各方面资料总结一下

      面试中经常会被问到这个问题吧,唉,我最开始被问到的时候也就能大概说一些流程.被问得多了,自己就想去找找这个问题的全面回答,于是乎搜了很多资料和网上的文章,根据那些文章写一个总结. 写得不好,或者有意见 ...

    9. HTML5历史管理状态机制

      前言:想要不刷新页面同时改变url 可以用HTML5 window对象的 hashChange 事件.同时介绍两个相关的api 和 1个事件. 两个API:1.history.pushState({n ...

    10. sublime Text3 前端常用插件

      sublime Text3 前端常用插件 - File Switching (文件切换) --- Sublime Text提供了一个非常快速的方式来打开新的文件.只要按下Ctrl+ P并开始输入你想要 ...