MQTT版本升级过程及源码解析


  首先说一下为什么要写这篇文章呢,在我发现网上对MQTT的文章介绍实在太少了,可能也是使用这个的频率比较低吧!还有对问题的定位以及解决的方式和办法也太少了,所以特意写这篇文章希望能作出一些贡献,帮助到一些需要的人。

  主要记录一下MQTT在原先1.2.0版本使用过程中出现的问题,排查问题到升级1.2.1版本过程中出现的问题,通过源码一步步排查出最后的问题点,直到符合预期目标。

 

<!-- https://mvnrepository.com/artifact/org.eclipse.paho/org.eclipse.paho.client.mqttv3 -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.1</version>
</dependency>

  MQTT的搭建及SSL认证可以参考这个博客:https://www.cnblogs.com/yueli/p/7490453.html 在这不仔细阐述


 Qos介绍 

  在开头先了解一下Qos的一些含义,也是这个问题为导火线

  • Qos = 0  最多一次的传输

       发布者PUBLISH消息到服务器(broker),发送即丢弃。没有确认消息,也不知道对方是否收到。网络层面,传输压力小

  • Qos = 1  至少一次的传输

   发布者发布消息保存消息,服务器(broker)接收到消息,服务器(broker)PUBLISH到订阅者,服务器(broker)回一个PUBACK信息到发布者让删除消息,然后订阅者接收消息后PUBACK给服务器让删除消息。如果失败了,在一段时间确认信息没有收到,发送方都会将消息头的DUP设置为1,然后再次发送消息,消息最少一次到达服务。例如网络延迟等问题,发布者重复发送消息,订阅者多次订阅重复消息 

  • Qos = 2  只有一次的传输

  其实Qos = 2 只是在 1 的基础上做了改掉的赶脚,在发布者PUBLISH到服务器之后多了消息的确认以及多了消息msgID的缓存,重复信息的去重。在服务器PUBLISH到订阅者之后也多了消息的确认。

  三种情况的区别

   0 没有保存message,没有重发机制,啥事也不知道,1和2 的发布者和服务器有保存message,发布者有重发机制,服务器都有PUBLISH之后的PUBACK的确认机制,但是2的服务器多了缓存msgID的一项功能,提供了去重功能,防止了消息的重复发送,以及消息的接收的确认机制。订阅者这边不过多的介绍,感兴趣再去了解。


MQTT1.2.1版本出现的问题 

  问题的描述:因为项目中使用MQTT通信的地方比较多,一般都是以Qos = 0 的形式,这段时间发现会时常提醒报错Too many publishes in progress (32202),看了一下源码报错地方

  从这里可以判定actualInFlight超出自己设置的maxInflight最大值导致的,尝试加大maxInflight也无用,只是延迟报错的时间而已


问题排查过程

  首先上网搜一下是否有类似问题出现,果不其然有个哥们碰到了,博客地址:https://blog.csdn.net/lblblblblzdx/article/details/81159478 此文章给我很大帮助,感谢博主,但是最后的解决方案不是很好。

  跟踪发布过程的源码逻辑

  第一步:publish的过程

//
public IMqttDeliveryToken publish(String topic, MqttMessage message, Object userContext,IMqttActionListener callback)
//
public void sendNoWait(MqttWireMessage message, MqttToken token)
//
void internalSend(MqttWireMessage message, MqttToken token)
//
public void send(MqttWireMessage message, MqttToken token)

  第二步:所有Qos类型,在publish消息的顶级父类中的构造器默认设置msgId = 0  

  第三步:消息在send方法中做了主要处理

  第四步:根据上面的Qos的介绍,说明我们publish的消息在这个地方全部都缓存在tokens这里,其实就是放入Hashtable中,不管什么等级的Qos

  在这几个步骤中,已经缓存好信息,准备异步发送,其中的lock机制就不多去解释

  第五步:异步发送,其实主要是在客户端链接完成的时候就已经开始监听了,connect流程

 //
public IMqttToken connect(MqttConnectOptions options) //2 异步链接
ConnectActionListener connectActionListener = new ConnectActionListener(this, persistence, comms, options,userToken,userContext, callback, reconnecting);
connectActionListener.connect(); //3 客户端的通信链接,包括发送和接收
ClientComms.connect(options, token); //4 个人理解是 将通信信息塞进线程池中,分别开启发送和接收的线程处理
ConnectBG conbg = new ConnectBG(this, token, connect, executorService);
conbg.start();

  第六步:在ConnectBG的run中 new CommsSender. start线程run中while循环发送的信息流程,一直发送消息中

  第七步:在notifySent的方法中判断Qos = 0 的作出了判断及操作

  这些差不多就是发布过程的主题流程结构,了解这些才会知道让你解决问题更加的容易


  问题点

  在上面的第六步的图中黄色箭头指出了问题出现的点,主要是在大数据量高并发的时候,因为在Qos = 0 的时候,在tokens(Hashtable)中的key一直是0,默认初始化。后面的流程中并没有改变过,在黄色那块tokenStore.getToken 在发送之后才remove数据,但是多条数据高并发的时候,在remove数据之后,后一条在get的时候会出现空的状况,不发送信息,导致actualInFlight没有减,一直增加,一定时间后就会超出最大值。Qos =1 、2 是不会发送这样的情况,因为他们的messageId是唯一的。


  解决办法

  1. 将Qos设置为1 (这是网上主流的解决办法,但是这个太耗费资源问题,在我看来只是规避问题点而已)
  2. 升级MQTT版本1.2.1    (这个版本解决了刚才说的bug)      
  3. 也可以想想在不升级版本的情况下如何去改善这个问题,重写那些类可以实现···

  


  解决过程

  既然说了第一种解决办法不是很赞同,那就直接进去第二种办法吧。升级到1.2.1版,时间:Feb, 2019,但是在升级版本的时候又出现了一些问题,因为改动还是有点多的。

  接下来说说1.2.1版本的改动了什么呢


  首先主要改动的是在Qos = 0 的不放入tokens中了,首先想到的是不会get到了,也不用担心重复了,直接从集合pendingMessages中拿

  然后从信息的自身获取数据的token,获取不到再去tokens中拿

  以上两个就是主要解决这个高并发冲突的原因


  跳入另一个坑中···

   为什么这么说呢,在我们开发的意识中,升级版本怎么也要向下兼容吧,那就顺其自然的换个版本就完事了,结果一跑起来,一堆红色的出现,那心情···我太南了。以我的性子,就是不能惯着她,继续深挖为什么,既然坑已经有了,就不怕有多深。

   另一个坑的过程

  第一时间也是上网搜一些为啥,大家的解决也差不多,都是SSL验证出现问题,但是解决方案也是五花八门


  解决方案

第一种:设置系统属性  类似这样的文章 https://blog.csdn.net/hxpjava1/article/details/77937026

   第二种:有看了一些代码了,就是重写X509TrustManagerImpl,绕过SSL的验证,试过有用,类似这样的文章 https://blog.csdn.net/iverson_AL/article/details/100669777

   第三种:再深入看一些源码,你会看到会什么会报错,为什么会验证,主要是你的属性没有设置好,采用了默认验证导致


  解决过程

  这里主要说一下解决的过程,如何从这些网络文章种找出适合自己的出路。

  首先 第一种我就不咋喜欢,动不动就设置了系统全局属性,第二种方案,有两种可能性,一是这个api真的有问题或是不符合自己的项目需要重写代码逻辑结构,二是在不了解的情况下直接绕过验证。我在测试第二个的时候就是如此只是稍微看了一下源码,没有深入看进去,试了一下,果真可以实现我想要目的。

  但是过后又想了一下,不该如此,既然开源出来的东西,不可能如此**,应该会有什么地方可以简单设置一下的。既然有这个想法就一直深入探究下去,果不其然,真相出来了。

  在1.2.1版本中MqttConnectOptions 的httpsHostnameVerificationEnabled属性默认true,导致不是Https的被验证不通过导致的,也可能MQTT开发人员安全意识很强,在1.2.0版本中没有这个概念存在,所以在版本升级的时候需要加上MqttConnectOptions.setHttpsHostnameVerificationEnabled(false);

  以下源码查看的过程

  若是有Https证书是不会有问题的。


  总结一下

   其实很简单的问题,居然整的时候那么复杂,原因是我们不够强,面对源码的时候还是比较害怕的,还有比较懒吧。

   整个过程比较繁琐,啰嗦吧,耐心看下来,应该有收获。

   操作总结:

    1. 升级MQTT版本1.2.1
    2. 若不是https的需要设置为false
    3. 不要走,跑起来

  扩展点及疑惑地点可以供参考

  

转载请注明出处  https://www.cnblogs.com/zhouguanglin/p/11986446.html

MQTT版本升级过程及源码解析的更多相关文章

  1. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  2. 解析jQuery中extend方法--源码解析以及递归的过程《二》

    源码解析 在解析代码之前,首先要了解extend函数要解决什么问题,以及传入不同的参数,会达到怎样的效果.extend函数内部处理传入的不同参数,返回处理后的对象. extend函数用来扩展对象,增加 ...

  3. QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)

    前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...

  4. 分布式事务_02_2PC框架raincat源码解析-启动过程

    一.前言 上一节已经将raincat demo工程运行起来了,这一节来分析下raincat启动过程的源码 主要包括: 事务协调者启动过程 事务参与者启动过程 二.协调者启动过程 主要就是在启动类中通过 ...

  5. Tensorflow版Faster RCNN源码解析(TFFRCNN) (2)推断(测试)过程不使用RPN时代码运行流程

    本blog为github上CharlesShang/TFFRCNN版源码解析系列代码笔记第二篇   推断(测试)过程不使用RPN时代码运行流程 作者:Jiang Wu  原文见:https://hom ...

  6. Fabric1.4源码解析: 链码容器启动过程

    想写点东西记录一下最近看的一些Fabric源码,本文使用的是fabric1.4的版本,所以对于其他版本的fabric,内容可能会有所不同. 本文想针对Fabric中链码容器的启动过程进行源码的解析.这 ...

  7. Fabric1.4源码解析:链码实例化过程

    之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程.其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的. 同样,切入点仍然是fabr ...

  8. Flink 源码解析 —— JobManager 处理 SubmitJob 的过程

    JobManager 处理 SubmitJob https://t.zsxq.com/3JQJMzZ 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink 从0到1 ...

  9. Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程

    TaskManager 处理 SubmitJob 的过程 https://t.zsxq.com/eu7mQZj 博客 1.Flink 从0到1学习 -- Apache Flink 介绍 2.Flink ...

随机推荐

  1. MAC配置JAVA环境变量

    一.下载安装文件 地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html jdk-8u144-macosx-x64. ...

  2. 「考试」$5T$

    啊因为最近题实在是好啊,只能四五篇四五篇写了. T1. 括号序列的确简单. 当我们维护左右$cnt$后. 到一个左括号的地方的话. 答案就是:$$\sum\limits_{i=1}^{min(lc,r ...

  3. NOIP模拟 29

    T1第一眼觉得是网络流 看见4e6条边200次增广我犹豫了 O(n)都过不去的赶脚.. 可是除了网络流板子我还会什么呢 于是交了个智障的EK 还是用dijkstra跑的 居然有50分!$(RP--)$ ...

  4. Mokia(三维偏序)P4390

    提到cdq,就不得不提这道该死的,挨千刀的题目了. 极简题面: 给定一个二维平面,在ti时刻会在(xi,yi)放一个点,会在tj时刻查询一个方框里面的点的数量 看道题就是二维线段树乱搞啊,这么水??? ...

  5. ORACLE存储过程的创建和执行的简单示例和一些注意点

    此示例的主要目的主要是为了了解在PL/SQL环境下怎么创建和执行存储过程. 存储过程所涉及的DataTable: 第一步:创建游标变量 游标是ORACLE系统在内存中开辟的一个工作区,主要用来存储SE ...

  6. 【html css js】实现一个简易日历

    ——[效果预览] 实现了日历最基础的功能,当前日期红色显示,可通过上方的左右按钮查看上一月或下一月的日期. ——[代码部分] 1. HTML <body> <div class=&q ...

  7. TCP--文件上传

    客户端 public class Test2_UpdateClient { public static void main(String[] args) throws UnknownHostExcep ...

  8. 解析XML数据,必看

    xml源文件 <?xml version="1.0" encoding="UTF-8"?> <humans> <zhangying ...

  9. 简单的倒叙应用---倒序打印字符串(C语言)

    void reverseStr(char* str){ if(*str=='\0'){ return; } reverseStr(str+1); printf("%c\n",*st ...

  10. 极&#183;Java速成教程 - (1)

    序言 众所周知,程序员需要快速学习新知识,所以就有了<21天精通C++>和<MySQL-从删库到跑路>这样的书籍,Java作为更"高级"的语言也不应该落后, ...