MQTT协议内部分享
时间:2018-07-26
讲解提纲
- 协议文档的解读
- 协议适用的场景
- 协议正确的使用方式
技术知识准备
需要理解计算机网络分层模型的工作原理,因为MQTT是一个应用层的协议。
图片来源:HiveMQ官网
https://www.hivemq.com/blog/mqtt-essentials-part-3-client-broker-connection-establishment
Q: 什么是网络连接?
A: 网络连接是传输层定义的概念,在传输层以下只存在网络数据包的相互交换。
所谓连接,其实也不是在网络上有一条真实存在的数据通道。只要通信双方在一段时间内持续保持数据包交换,就可以视为双方建立的连接并没有断开。
连接的建立是依托于TCP协议的三次握手,一旦连接已经建立完毕,通信双方就可以复用这条虚拟通道进行数据交换。如果连接保持长时间工作一直没有被中断,那么这样的TCP连接就俗称为长连接。
MQTT协议介绍
1. 协议全称
Message Queue Telemetry Transport
,中文直译:消息队列遥测传输协议。
2. 版本历史
1999年,IBM和合作伙伴共同发明了MQTT协议。
2004年,MQTT.org开放了论坛,供大家广泛参与。
2011年,IBM建立了Eclipse开源项目Paho,并贡献了代码。
2013年,OASIS MQTT技术规范委员会成立。
2014年,MQTT正式成为推荐的物联网传输协议标准。
3. 适用场景
- 物联网设备 - - - - 设计初衷
在MQTT协议被设计出来的年代,还没有物联网这么时髦的词汇,当年叫做遥测设备。
- 移动互联网 - - - - 时代驱动
MQTT协议真正开始声名鹊起的原因,是其正好恰恰踩中移动互联网发展的节拍,为消息推送场景提供了一个既简便又具有良好扩展性的现成解决方案。
4. 协议初步解读
(1) 看命名
Message Queue -- 主要目的
提供消息的投递服务
保障消息投递的可靠性
Telemetry Transport -- 应用场景
低带宽
网络不稳定
设备性能不足
非常契合移动互联网发展初期的网络与设备实际工作场景。
(2) 看官方文档摘要
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
(3) 看消息头定义
Fixed Header
Header Structure
图片来源:
来自: http://www.steves-internet-guide.com/mqtt-protocol-messages-overview/
解读
可以看出,MQTT对消息头的规定十分精简,固定头部占用空间大小仅为1个字节,一个最小的报文占用的空间也只有两个字节(带一字节的长度标识位)。
这也是MQTT协议针对不稳定及带宽低下的网络环境做出的特定设计 - - - - 尽可能地节省一切不必要的网络开销。
5. 控制报文 -- Control Packet
- 可归类为三组
功能点 | Control Packet |
---|---|
连接相关 | CONNECT, CONNACK, PINGREQ, PINGRESP, DISCONNECT |
消息发布相关 | PUBLISH, PUBACK, PUBREC, PUBCOMP |
消息订阅相关 | SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK |
Q:为什么MQTT协议需要心跳报文(PINGREQ, PINGRESP)来维护连接状态,只监控该TCP的连接状态是否可以实现目的?
A: TCP数据传输默认的超时时间过长,不符合应用层上细粒度的要求。
TCP数据传输超时的情况可分成三种:服务端断开
、客户端断开
、中间网络断开
。
在前两种场景下,若断开操作是一方主动发起的,即表示为TCP连接正常结束,双方走四次挥手流程;若程序异常结束,则会触发被动断开事件,通信另一方也能立刻感知到本次连接所打开的Socket
出现中断异常。
唯独中间网络的状态是通信双方不能掌握的。在Linux系统下,TCP的连接超时由内核参数来控制,如果通信中的一方没有得到及时回复,默认会主动再尝试6次。如果还没有得到及时回应,那么其才会认定本次数据超时。
连带首次发包与六次重试,Linux系统下这7次发包的超时时间分别为2的0次方至2的6次方,即1秒、2秒、4秒、8秒、16秒、32秒、64秒,一共127秒。MQTT协议认为如此长的超时时间对应用层而言粒度太大,因此其在应用层上还单独设计属于自身的心跳响应控制。常见的MQTT连接超时多被设定为60秒
。
扩展知识 - TCP的KeepAlive机制:http://hengyunabc.github.io/why-we-need-heartbeat/
注:KeepAlive机制与上文叙述的超时场景不同。
KeepAlive是TCP协议对空闲已久的信道所增加的额外检测机制。
6. 协议的工作方式
(1) 通信方式
Client <==> Server - - - - 传输过程
Client <==> Server <==> Client - - - - 传达模式
Server 的专有称谓 - - - - Broker(代理人)
(2) 匹配方式
Topic主题,相当于投递地址,以UNIX路径方式命名
发布消息:需要指定消息Topic的具体路径,如 /topic/A/weather
-
订阅消息:两种方式
指定路径订阅 - - /topic/A/weather
模式匹配订阅 - - /topic/+/weather, /topic/#
7. Qos保障设计
由通信中的报文标识符( Packet Identifier
)传达。
-
Qos = 0
: At most once delivery- 一次数据交换:
Publish
- 一次数据交换:
-
Qos = 1
: At least once delivery- 两次数据交换:
Publish, Pubrec
- 两次数据交换:
-
Qos = 2
: Exactly once delivery- 四次数据交换:
Publish, Pubres, Pubrel, Pubcomp
- 四次数据交换:
Q:仅Publish与Pubrec能保证消息只被投递一次吗?
A:业务上可以实现,但MQTT协议并没有如此设计,原因如下:
每个消息都会拥有属于自己的报文标识符,但如果需要两次数据交换就实现消息仅只收到一次,就需要通信双方记录下每次使用的报文标识符,并且在处理每一条消息时都需要去重处理,以防消息被重复消费。
但MQTT协议最初被设计的工作对象是轻量级物联设备,为此在协议的设计中报文标识符被约定为可重用,以减少对设备性能的消耗,换回的代价不得不使用四次网络数据交换,才能确保消息正好被消费一次。
额外参考资料:https://stackoverflow.com/questions/41329267/mqtt-qos2-why-use-4-packets
Q:两个不同客户端在发布与订阅同一Topic下的消息时,都可以提出通信Qos要求,此时以哪项为基准?
A:伪命题,故意在分享时埋下坑,等人来踩。
两个不同客户端的通信是需要Broker
进行中转,而不是直连。因此,通信中存在两个不同的会话,双方的Qos要求仅仅作用于它们与Broker
之间的会话,最终的Qos基准只会向最低要求方看齐。
8. 特殊消息
(1) 遗嘱消息 - - - - Last Will Message
绑定至Topic上,每个Topic最多绑定一条遗嘱消息
客户端断线后,服务器会将遗嘱消息发布在此Topic上
(2) 保留消息 - - - - Retain Message
绑定至Topic上,每个Topic最多绑定一条保留消息
客户端订阅此Topic后,将立刻收到此条消息
一个遗嘱消息也可以同样是保留消息,两者不冲突
(3) 工作方式
- 仅对消息的发布时机与发布方式制定规范,使用权移交至业务方
例:遗嘱消息的正确使用方式可参考此篇文章:https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament
- 消息直接转发,并不持久化存储
虽然可以借助Retain Message实现绑定一条消息至某个Topic,以达到消息的暂时保留目的。
但首先Retain Message并不是为存储场景而设计的,再次MQTT协议并没有对消息的持久化作出规定,也就是说Broker重启后,现有保留消息也将丢失。
Q:两种特殊消息的使用场景?
A:遗嘱消息,多用于客户端间获取互相之间异常断线的消息通知;
保留消息,可保存最近一条广播通知,多用于公告栏信息的发布。
MQTT应用实践
1. 开源Broker
项目使用
Eclipse Mosquitto
:MQTT协议的最小集实现
所谓最小集实现,是指仅仅实现协议所规定的基本功能。
协议规定之外的,比如消息的持久化存储、设备的连接管理等实际投产所需要的重要功能点,都需要业务方自行设计。
其他
有EMQ
, HiveMQ
, RabbitMQ MQTT Adapter
等。
2. 协议扩展
- SSL/TLS扩展:传输层安全保障
需要在客户端与服务端中手动配置证书,较繁琐。
- MQTT Over Websockets:基于
WebSockets
协议的实现版本,可供浏览器使用,可直接复用HTTPS网站证书
原因:浏览器上不能直接使用原生TCP协议。
好处:可直接复用网站证书,节省工作量。
- UDP版本:MQTT for Sensor Networks – MQTT-SN协议
注意:MQTT-SN与MQTT并不是一个协议,在设计上有一些不同。
官方文档表示MQTT-SN被设计的场景是传感器网络,因为其网络质量不足以支撑起一对稳定的TCP连接。
3. 业务系统的注意点
(1) Qos=2投递保障
Qos=2
消息保障的网络I/O次数过多,如果不是必需,尽少在程序里使用此类消息。
毕竟当初其设计的目的是为了减少设备的性能占用,但若应用场景并不是物联网,而是用于手机、电脑或浏览器端等现在已不缺性能的设备上,最好在报文体中,使用UUID生成全局唯一的消息ID,然后自行在业务解析中判断此报文是否被消费过。
或者,业务方在处理消息时保证其被消费的幂等性,也可消除重复消息对系统带来的影响。
(2) 业务端的逻辑心跳
正如MQTT协议并没有依赖TCP连接状态,自己在应用层协议上实现心跳报文来控制连接状态,业务方作为MQTT协议的使用者,也不要完全依赖协议的工作状态,而是依托MQTT协议建立属于业务本身的信息汇报机制,以加强系统的稳健性。
例1:MQTT连接超时默认设为60秒,对于多数互联网应用而言,其直接面对终端用户,这么长的时延显然值得商榷。
例2:业务中不同的终端可能均属于同一个用户ID,那么仅仅依托MQTT协议的遗嘱报文,不足以判断用户是否在线。
(3) Retain Message
Retain Message
可视为客户端主动拉取的行为。如果业务系统采用HTTP+MQTT双协议描述业务过程,主动拉取的操作也可使用HTTP请求替代。
(4) 机器资源的限制
作为一个长连接型的应用,上线前需要根据业务量级,评估对操作系统端口数与文件描述符的占用要求,以防服务器资源被打满。
4. Mosquitto实践
(1) 超时默认配置
连接异常断开:60秒
心跳发送间隔:20秒,心跳回应超时:10秒
Pingreq由客户端发起,服务器回应Pingresp。
客户端在
空闲
时会不断向服务端发起心跳状态维护请求。其发起一个Pingreq报文后,默认10秒没有收到回应后,会立刻发起新的Pingreq报文;如果有回应,20秒后再次重发请求。服务端若60秒内没有收到客户端的Pingreq报文,或者没有收到来自此客户端的发布消息,就视为此客户端已断开连接。
(2) 消息缓存的限制
在服务端的配置文件和客户端的连接参数中,都拥有max_inflight_messages
此项配置,来维护Qos=1 or 2
消息是否被成功消费的状态。
MQTT
最初被设计为物联网级的通信协议,因此此参数的默认配额较小(大多数情况下被限制到10至20)。
但如果将MQTT协议应用至手机、PC或Web端的推送场景时,硬件性能已不在是瓶颈,在实际使用中推荐把此参数调大。
Broker
中的此配置可调为-1
,表示无限制;运行在服务器上的
MQTT-Client-as-Service
,设置至5000
以上为佳;用户操作端上可相对保守一些,因为其不需要消费大量消息,可设为
50-100
左右。
(3) 高可用性保障
Mosquitto提供Bridge功能,需要我们自己配置。
Bridge
意为桥接,当我们把两台Broker桥接在一起时,只需要修改一台Broker的配置,填上另一台Broker的运行地址。前一台Broker将作为客户端发布与订阅后一台Broker的所有Topic,实现消息互通的目的。
两台Broker桥接之后,它们均可以作为接入节点提供服务,对外可配置一个虚拟IP或域名来访问,与MySQL主备同步的读写分离方案有所不同。
桥接带来的问题有以下几点:
a) 为了保障服务的可用性,通常将两台服务器部署于不同的机房中。两个机房之间的访问时延需要尽可能低,否则消息的收发将会异常缓慢。
b) 增加一台Bridge后网络流量会翻倍,增加两台流量就翻两倍,因此会加大对网络的承载能力要求。
c) 在配置Bridge时,需要注意不能配置成消息环路,否则消息将会无限循环。
d) 两个机房的网络互联出现故障时,消息的同步将会出问题,影响业务的可用性。
我的建议:
a) 在配置Bridge时,可以指定消息同步的Topic,而不是同步所有来规避流量放大的问题。
不过更推荐通过垂直切分的方案进行分流。作为消息队列,只需要传递索引信息即可,文件传输不要走消息队列。b) 若业务上不方便实现垂直切分的方案,可根据业务特点开发长连接代理,从而实现消息的定点投放。
因为无论是桥接转发还是消息同步,面临的问题都是每台接入服务器都将接收全量消息,在大流量的背景下网卡速率会成瓶颈,解决方案还是采取水平切分的思想,避免消息流量的全面广播。c) 使用两台Broker配置Bridge时,为了避免机房间网络故障而造成的业务问题,最好引入可信的第三方存储组件(如数据库、ZooKeeper、Consul等),实现对监控信息的保存。若Brokers之间网络出现互联故障,推荐通过抢主方法,关停一台以临时解决业务上消息不同步的问题。
总结 与 QA
1. 请问MQTT 与 Websockets 之间的区别?
- Websockets
Websockets协议被设计的目的是为浏览器提供一个全双工的通信协议,方便实现消息推送功能。
在Websockets协议被设计出来前,受限于HTTP协议的一问一答模型,消息的推送只能靠轮询来实现,在资源消耗与时效性保障上,均难以达到令人满意的效果。
Websockets协议复用了HTTP协议的头部信息,告知浏览器接下来的操作将触发协议升级,然后通信双方继续复用HTTP的Header,但报文内容已转变为双方均接受的新协议的格式。
Websockets协议改进了网页浏览中的消息推送的方式,因此被广泛应用在聊天、支付通知等实时性要求比较高的场合下。
- MQTT Over Websockets
MQTT协议重点在于消息队列的实现,其对消息投递的方式作出约定,并提供一些额外的通信保障。
MQTT可采取原生的TCP实现,也有基于Websockets的实现版本。当然后者在网络字节的利用率上,不如前者那么精简。但浏览器端无法直接使用TCP协议,所以就只能基于Websockets协议开发。
不过基于Websockets的应用也有方便之处:一是证书不需要额外配置,直接与网站共用一套基础设施;二是可使用Nginx等工具管理流量,与普通HTTP流量可共用一套配置方法。
2. 本次分享的总结
(1) 学会理解网络协议
MQTT非常适合入门,原因如下:
a) 简单:相比较著名的HTTP协议,或者消息队列中常用的AMQP协议,MQTT协议被设计得足够简单,因此脉络的整理也相应方便很多。
b) 场景固定:MQTT协议被设计用来解决低宽带高延迟下的消息投递问题,目标明确。而像HTTP协议,互联网上大部分内容都需要通过此访问,面对的问题远比MQTT复杂,因此理解起来不是那么直接。
c) API简单:与上面两点相辅相成,方便上手实践。
(2) 学会理解系统设计
实际的应用场景远比理想中的复杂,无法一招走遍天下,必须做好取舍。
MQTT协议在这方面做得很优秀,以后工作中可以作为参考,设计好自己负责的业务系统。