issue 1. EMQX的共享订阅

EMQX是一个非常强大的物联网通信消息总线,基于EMQX开展应用开发,要注意很多配置细节问题,这里要说到的就是共享订阅以及和cleanSession之间的关系问题。共享订阅在EMQ的里程牌中出现的较早,V2的时候就已经提供了,只是那个时候只支持单节点的共享订阅,在V3的时候才支持集群的共享订阅。

共享订阅功能非常实用,解决了消费者应用程序的负载均衡问题,或者说高可用问题。否则,负载均衡或者高可用问题,需要借助于全局锁进行消息消费过程中的只消费一次的问题。这个实现起来,相对比较的麻烦,主要是稳定性和并发支持上要花很多精力调优。现在EMQX已经做了这个,已共享订阅的方式为应用程序的开发提供了便利。

共享订阅支持两种方式:

订阅前缀              使用示例
$queue/ mosquitto_sub -t ‘$queue/topic’
$share/<group>/ mosquitto_sub -t ‘$share/group/topic’

目前,我们的物联网平台中采用的是$share/<group>这种方式,主要有$share/dcX/yourTopic,和$share/resX/yourTopic这两种应用场景。

使用共享订阅,需要注意这里会遇到的潜在风险,和cleanSession的配置相关(cleanSession干什么用,自行查阅研究),这里我就拿我们项目上遇到的故事,分享一下:

  • 我们的应用程序res部署了2个应用实例,emqx集群也是2个节点,其实这里和emqx集群节点数没有关系(理论分析)。res应用程序里面,cleanSession设置的是false,也就是说应用程序和EMQX的mqtt连接断掉后,emqx服务端还会保留session一个配置指定的时间。我们使用的emqx是V3.1.1,默认的session有效期是2h。共享订阅的策略是默认的random。res应用程序里面的消费者clientId是uuid随机生成的,每次运行都不一样,一旦程序启动后,ClientID就固定下来。
  • 我们的测试动作是这么干的,正常启动两个res服务a和b,一切正常,共享订阅消息没有问题。测试中途,将其中一个res应用a停机了,在b上继续验证业务逻辑,发现在b上一会收的到消息,一会又收不到消息,同样的订阅逻辑,为何停掉了一个应用,就会出现这种现象?
  • 上述现象发现后,大概半小时的分析,无果。然后,将停掉的res应用a再次启动,发现,还是会出现a,b,上同样有可能都收不到消息,订阅逻辑没有问题啊,怎么还是解决不了问题?
  • 开始怀疑res应用程序订阅逻辑写的不对,将多个topic逐个订阅改成数组的模式一次订阅,继续部署继续测试,发现订阅不到消息的情况更明显了,这咋解释啊?

其实,当知道是res应用程序反复启停,在res上订阅不到消息的现象越发明显。这个线索非常有价值,这就提醒了问题所在了!其实就是因为cleanSession为false的情况下因为ClientID在每次启停res的时候以一个新的身份进行共享订阅了,对于EMQX来说,相当于共享订阅者变多了,每次启停,就产生了一个虚拟的共享订阅者,若我们不注意,其实是看不到的,因为我们只是关注了实际的两个res应用a和b。若你细心一点,你可以在emqx的dashboard上能够看到不止2个订阅者。(参看下面几个图)

图1:正常订阅(正常的两个共享订阅者)

图2:将res a应用移除(相当于在一段时间内,EMQX认为有2个共享订阅者,session为超期)

图3 将res a移除后,又加入进来共享订阅(相当于在一段时间内,EMQX认为有3个共享订阅者,session为超期)

图4 将res a移除后,加入进来,然后又移除,然后又加进来(相当于在一段时间内,EMQX认为有4个共享订阅者,session为超期)

解决这个问题,其实有两个方向
1. ClientID不能变化,每次res启停都是相同的,不管什么时候,一个res应用,对应的ClientID要恒定不变。可以基于app_ip+app_port+emqx_ip这种类似思路,进行锁定ClientID。这样EMQX这边就不会出现虚拟的消费者连接。
2. 将cleanSession配置为true,每次mqtt连接断掉后,EMQX端就不要继续保持对应连接的session,EMQX这里就会立即踢掉断线的session,不会出现潜在的接收消息的订阅者,此种情况下,及时ClientID每次不一样,也不会出现虚拟消费者。

issue 2. ACL校验

EMQX对于接入的连接,不论是想订阅和消费,首先要经过权限校验,符合ACL规则的,才允许进行后续的数据流转。但是,EMQ基于插件的方式实现auth功能,这里,我们采用的是基于mysql实现认证和acl。其实身份认证还不是多大的问题,主要是acl比较消耗mysql的性能。尤其是在连接数比较多的时候。

  • 我们的压测环境下,mysql是单点库,机器配置4C24G500G。 EMQX节点2个,在连接数1W以上,每个连接每秒2个消息的情况下,mysql数据库开始出现CPU占用比较高的现象,CPU 60%的消耗。
  • 当时ACL表中的记录数并不是太多,才10多万,通过show full processlist查看SQL信息,发现很多都是EMQX的emqx_auth_mysql.conf文件里面的acl_query的语句。说明这个查询比较频繁,且占用时间有点长。觉得这个不应该吧,并发不是很高啊。。。
  • emqx的ACL cache配置ttl是默认的1分钟,最大缓存数量cache size是默认的32.
  • emqx_auth_mysql.conf中的查询语句:auth.mysql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'。 检查了下这个SQL的执行计划,得到下面的结果:

    mysql> explain select allow, ipaddr, username, clientid, access, topic from scc_mqtt_acl_1 where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c';
    +----+-------------+----------------+------------+------+---------------------------------------+------+---------+------+--------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+----------------+------------+------+---------------------------------------+------+---------+------+--------+----------+-------------+
    | | SIMPLE | scc_mqtt_acl_1 | NULL | ALL | doubleIndex,USERNAME_IDX,CLIENTID_IDX | NULL | NULL | NULL | | 34.39 | Using where |
    +----+-------------+----------------+------------+------+---------------------------------------+------+---------+------+--------+----------+-------------+
    row in set, warning (0.00 sec)

    可以看到,这里是没有用到索引的。即,在设备数据量很大的时候,这里是存在隐患的,即查询会全表扫描。说明emqx默认的关于ACL的查询需要结合应用场景进行优化。

  • 分析了我们的应用场景,每个设备接入都有自己的用户名和密码,ipaddr是变化的,也就是说不好进行监控管理,就不做强制要求,所以可以忽略不管,ClientID,这个对于应用来说,也是基于一定规则进行的,不会出现重复的信息,所以,我们也没有做强制要求, 在ACL配置的时候,主要是基于username和ClientID进行标识记录的,username是必须要有的,ClientID不是必须的,所以,我们的ACL查询语句就优化了下,将原来默认的查询where中的几个or改了,只是一个username了。如下:

    auth.mysql.acl_query = select allow, ipaddr, username, clientid, access, topic from scc_mqtt_acl_1 where username = '%u'
    给数据表的username创建一个normal的索引,此时的执行计划如下:

    mysql> explain select allow, ipaddr, username, clientid, access, topic from scc_mqtt_acl_1 where username = '%u';
    +----+-------------+----------------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+----------------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
    | 1 | SIMPLE | scc_mqtt_acl_1 | NULL | ref | USERNAME_IDX | USERNAME_IDX | 403 | const | 1 | 100.00 | NULL |
    +----+-------------+----------------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.04 sec)

    可以看到,索引生效了,取代默认配置or语句,用上索引。

issue 3. emqx稳定性问题

我们的压测目标,就是在两个EMQX节点集群环境(4C16G100G)下,能够支持至少10000的TPS(每秒10000条消息被消费),目标20000个连接。 下面的几种case中,消费者程序是一样的。生成消息的机制有点变化,但是消息报文大小是一样的。

case 1.

在用V3.0.1的时候,进行压测,并发20000TPS,100连接。出现下面的错误信息,提示消息处理不过来,EMQX将消费者进行剔除。

-- ::38.916 [error] 39346787c9bc4afd990a16a2700995fd@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::49.827 [warning] [SYSMON] large_heap warning: pid = <0.5103.>, info: [{old_heap_block_size,},
{heap_block_size,},
{mbuf_size,},
{stack_size,},
{old_heap_size,},
{heap_size,}]
[{initial_call,{proc_lib,init_p,}},
{current_function,{lists,foldl,}},
{registered_name,[]},
{status,running},
{message_queue_len,},
{group_leader,<0.2133.>},
{priority,normal},
{trap_exit,true},
{reductions,},
{last_calls,false},
{catchlevel,},
{trace,},
{suspending,[]},
{sequential_trace_token,[]},
{error_handler,error_handler},
{memory,},
{total_heap_size,},
{heap_size,},
{stack_size,},
{min_heap_size,}]
-- ::50.300 [error] 9001547605ec46648b3c485103233946@10.95.198.26: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::57.398 [error] b34a92025fbc4c89bbe54ca22e74199f@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::57.922 [error] a74b0c24136b4c5899dc866fe57d9173@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::05.640 [error] 4f930fb242734f9ba22ce57c753c482b@10.95.198.26: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::30.424 [error] 39346787c9bc4afd990a16a2700995fd@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::32.180 [error] a74b0c24136b4c5899dc866fe57d9173@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::32.376 [error] b34a92025fbc4c89bbe54ca22e74199f@10.95.198.25: [Channel] Shutdown exceptionally due to message_queue_too_long
-- ::51.497 [error] 9001547605ec46648b3c485103233946@10.95.198.26: [Channel] Shutdown exceptionally due to message_queue_too_long

这个问题,在V3.0.1下测试很久,逐渐降低并发,降到13000左右才算稳定。但是这个过程中,遇到CPU消耗不稳定的现象,解决CPU在两个EMQX之间很不平衡,以及调整共享订阅的Strategy从random到round_robin启动失败,将版本调整到了V3.1.1

case 2.

在用V3.1.1的时候,同样进行压测,并发低些,但是要求连接数提升,至少过万,但是模拟的设备数量不到6000就出现问题。

-- ::15.479 [error] 10.95.198.31: ** State machine <0.14661.> terminating
** Last event = {timeout,}
** When server state = {idle,
{state,esockd_transport,#Port<0.418835>,
{{,,,},},
undefined,running,,
{pstate,external,
#Fun<emqx_connection.0.73284863>,
{{,,,},},
nossl,,<<"MQTT">>,<<>>,false,
<0.14661.>,undefined,undefined,
undefined,undefined,false,#{},,
undefined,undefined,undefined,false,true,
true,false,ignore,
#{msg => ,pkt => },
#{msg => ,pkt => },
false,undefined,false,
#{from_client => ,to_client => },
emqx_connection,#{},undefined},
{none,
#{max_packet_size => ,
version => }},
{emqx_gc,
#{cnt => {,},
oct => {,}}},
undefined,true,undefined,undefined,undefined,
undefined,}}
** Reason for termination = exit:idle_timeout
** Callback mode = [state_functions,state_enter]
** Stacktrace =
** [{gen_statem,loop_event_result,,[{file,"gen_statem.erl"},{line,}]},
{proc_lib,init_p_do_apply,,[{file,"proc_lib.erl"},{line,}]}]

这个问题,研究了很长时间,开始总以为是客户端程序写的有问题,改过几种版本的消息生产者程序,都是不成功,连接数上不来。后来查看EMQX在Github上的issue列表,发现有关于idle_timeout的merge request(https://github.com/emqx/emqx/pull/2675),我估计是不是知道内部有错误,做了修改。另外,关于这个问题,有人也提了issue,只是我觉得emqx的维护者并不是很nice的解答开发者的问题(https://github.com/emqx/emqx/issues/2686,这个issue的解答,我是觉得没有说清楚的)。于是,我将版本升级到了最新版本V3.2.3,再次测试这个连接数的问题,到目前为止,可以上升到13000的连接数,EMQX集群运行还算正常。

PS:

一个小小的经验,出现下面类似这样的警告,说明ClientID出现了重复,因为EMQX里面不允许出现重复的ClientID,若出现,将会阻断后接入的这个应用的接入。

-- ::52.083 [warning] 1_qemqeqseq68@10.95.198.31: [Channel] Discarded by 1_qemqeqseq68:<41446.2930.>
-- ::52.083 [warning] 1_qewf2p45b7k@10.95.198.31: [Channel] Discarded by 1_qewf2p45b7k:<41446.2733.>
-- ::52.083 [warning] 1_qem2iltm7eo@10.95.198.31: [Channel] Discarded by 1_qem2iltm7eo:<41446.32139.>
-- ::52.084 [warning] 1_qer8ey3os8w@10.95.198.31: [Channel] Discarded by 1_qer8ey3os8w:<41446.19217.>
-- ::52.087 [warning] 1_qeq2zud8kqo@10.95.198.31: [Channel] Discarded by 1_qeq2zud8kqo:<0.31128.>
-- ::52.088 [warning] 1_qeoe5lwdreo@10.95.198.31: [Channel] Discarded by 1_qeoe5lwdreo:<0.27662.>
-- ::52.088 [warning] 1_qeo1lfw2nls@10.95.198.31: [Channel] Discarded by 1_qeo1lfw2nls:<0.27702.>
-- ::52.091 [warning] 1_qex6mpfgnwg@10.95.198.31: [Channel] Discarded by 1_qex6mpfgnwg:<0.27882.>
-- ::52.091 [warning] 1_qemujku3vgg@10.95.198.31: [Channel] Discarded by 1_qemujku3vgg:<0.2020.>
-- ::52.091 [warning] 1_qemvx7mz3sw@10.95.198.31: [Channel] Discarded by 1_qemvx7mz3sw:<41446.3072.>

最后总结一下感受:

1. EMQX功能很强大,基本性能的确也非常不错,但是使用起来,尤其是想作为产品级别来使用,目前基于免费的版本,的确存在很多坑。

2. EMQX里面的确还有较多的不稳定问题存在,因为相关的资料的确太少,加上erlang语言,懂的不多,出现问题,调查起来非常的费劲,所以,还希望互联网用户能够积极分享经验。

MQTT研究之EMQ:【EMQX使用中的一些问题记录(1)】的更多相关文章

  1. MQTT研究之EMQ:【CoAP协议应用开发】

    本博文的重点是尝试CoAP协议的应用开发,其中包含CoAP协议中一个重要的开源工具libcoap的安装和遇到的问题调研.当然,为了很好的将EMQ的CoAP协议网关用起来,也调研了下EMQ体系下,CoA ...

  2. MQTT研究之EMQ:【EMQ之HTTP认证/访问控制】

    今天进行验证的逻辑是EMQ的http的Auth以及ACL的逻辑. 首先,参照HTTP插件认证配置的说明文档进行基本的配置, 我的配置内容如下: ##-------------------------- ...

  3. MQTT研究之EMQ:【JAVA代码构建X509证书【续集】】

    openssl创建私钥,获取公钥,创建证书都是比较简单的,就几个指令,很快就可以搞定,之所以说简单,是因为证书里面的基本参数配置不需要我们组装,只需要将命令行里面需要的几个参数配置进去即可.但是呢,用 ...

  4. MQTT研究之EMQ:【SSL证书链验证】

    1. 创建证书链(shell脚本) 客户端证书链关系: rootCA-->chainca1-->chainca2-->chainca3 ca caCert1 caCert2 caCe ...

  5. MQTT研究之EMQ:【EMQX使用中的一些问题记录(4)】

    最近比较忙,有些关于EMQ的使用问题,没有时间记录了,趁这个周末抽点时间,将最近遇到的,觉得比较有价值的一个问题,分享给大家吧. 这里是针对前面的一篇博客,做的一个深入研究,关于订阅系统总线判断设备上 ...

  6. MQTT研究之EMQ:【EMQX使用中的一些问题记录(2)】

    我的测试环境: Linux: CentOS7 EMQX:V3.2.3 题外话: 这里主要介绍Websocket的支持问题. 对ws的支持比较正常,但是对wss的支持,调了较长的时间,没有成功. Jav ...

  7. MQTT研究之EMQ:【EMQX使用中的一些问题记录(3)】

    EMQX功能强大,但是帮助信息或者可用资料的确有限,遇到个问题,比较难找到处理的头绪,今天,我要记录的是,使用中出现EMQX宕机,但是呢,启动也启动不了. 今天记录的内容,就以操作EMQX 3.2.3 ...

  8. MQTT研究之EMQ:【JAVA代码构建X509证书】

    这篇帖子,不会过多解释X509证书的基础理论知识,也不会介绍太多SSL/TLS的基本信息,重点介绍如何用java实现SSL协议需要的X509规范的证书. 之前的博文,介绍过用openssl创建证书,并 ...

  9. MQTT研究之EMQ:【基础研究】

    EMQ版本V2, emqttd-centos7-v2.3.11-1.el7.centos.x86_64.rpm 下载地址:http://emqtt.com/downloads/2318/centos7 ...

随机推荐

  1. MyCat教程四:实现读写分离

    本文我们来给大家介绍下通过MyCat来实现MySQL的读写分离操作 MyCat读写分离 一.读写分离配置   前面我们已经介绍过了mysql的主从同步和mycat的安装及相关配置文件的介绍,现在我们来 ...

  2. Gzip模块

    Gzip模块为python的压缩和解压缩模块,读写gzip 文件 一.使用gzip模块压缩文件: 1 import gzip #导入python gzip模块,注意名字为全小写 2 g = gzip. ...

  3. BST二叉树的二分查找

    900. 二叉搜索树中最接近的值 中文 English 给一棵非空二叉搜索树以及一个target值,找到在BST中最接近给定值的节点值 样例 样例1 输入: root = {5,4,9,2,#,8,1 ...

  4. hexo的jacman主题设置语言为英文后偶尔出现中文

    发现这个问题也好久了.问题的具体表现是在根目录下的_config.yml设置了语言为英文,但是每次发布后都会更换一次语言.今天看了文件结构,知道了,每换一次语言“英文.简体中文.繁体中文”,就是这三种 ...

  5. 项目Beta冲刺总结随笔

    班级:软件工程1916|W 作业:项目Beta冲刺 团队名称:SkyReach 目标:Beta冲刺Day2 项目Github地址 团队博客汇总 队员学号 队员姓名 个人博客地址 备注 22160010 ...

  6. "<<"和“>>”运算

  7. Unable to load bean org.apache.struts2.dispatcher.multipart.MultiPartRequest

    Unable to load bean org.apache.struts2.dispatcher.multipart.MultiPartRequest (jakarta) 把commons-ileu ...

  8. How would you differentiate JDK, JRE, JVM, and JIT?

    Q5. How would you differentiate JDK, JRE, JVM, and JIT?A5. There is no better way to get the big pic ...

  9. 笨方法学python3

    阅读<笨方法学python3>,归纳的知识点 相关代码详见github地址:https://github.com/BMDACMER/Learn-Python 习题1:安装环境+练习  pr ...

  10. 第8章 动态SQL

    8.1动态SQL中的元素 8.2<if>元素 举例,在映射文件中: <select id="findCustomerByNameAndJobs" paramete ...