查看: 2403|回复: 0

MQTT的技术实战案例

[复制链接]

7

主题

7

帖子

30

积分

新手上路

Rank: 1

积分
30
发表于 2019-2-8 15:25:59 | 显示全部楼层 |阅读模式
关于制造硬件与实现设备认证所带来的难题我们已经探讨过多次,今天咱们换个思路,看看在Go背后默默支持着它的软件方案。

在今天的文章中,我们将阐述Karma Go如何利用MQTT协议与Karma的后端基础设施相对接——MQTT协议属于一套定制构建的MQTT消息代理——以及如何通过一台代理服务器支持后端应用程序在内部通信过程中使用HTTP。

MQTT是什么,我们又为什么要使用MQTT?

Karma Go与Karma Classic都会定期收集状态信息(例如电池电量与信号强度等),并将其发送回我们的后端。除此之外,Karma设备还需要由后端提供信息来检测当前联网用户是否能够通过该设备访问互联网。

正如我们的CTO Stefan之前所解释,Karma Classic使用HTTP加TLS与我们的后端相对接,而由Karma Classic发出的状态信息则以小型数据包的形式存在。有鉴于此,我们觉得还有可能在技术架构与数据使用负载方面作出进一步优化,因此我们制定出了明确的解决方案改善思路——降低规模化难度、削减带宽使用率。

下面来说MQTT。这套轻量级消息收发协议专门面向那些以传输带宽以及电池电量为主要关注重点的场景;它显然非常适合Karma Go。为了取代原本对传输带宽要求较高的JSON数据包,我们转而使用协议缓冲区,并将这种二进制数据格式引入MQTT消息。

市面上并不存在能够满足我们全部需要的现成MQTT服务器方案,因此我们再次遵循老传统:亲自动手编写。

最后一步是要找到一套理想的MQTT服务器方案,它需要满足我们对于可扩展能力以及可用性的全部要求。我们希望这套MQTT服务器可以尽可能实现无状态化,这样才能保证Karma Go能够与任意后端服务器相对接。我们希望构建起MQTT服务器资源池,从而对负载进行妥善分配,并在一台甚至多台MQTT服务器发生故障时保持服务的正常运行。此外,我们还希望自己的后端服务(大部分由Ruby编写而成)能够通过HTTP与Karma Go进行通信,这样我们就不必为了使用MQTT而对服务进行全盘重写了。最后,我们希望选择开源解决方案,这样才能深入对其源代码进行查看与调整。

遗憾的是,市面上并不存在能够满足我们全部需要的现成MQTT服务器方案,因此我们再次遵循老传统:亲自动手编写。

MQTTParty由此诞生,欢迎各位莅临品鉴

从2014年底开始,我们正式着手构建这样一套具备高可用性、高可扩展性的MQTT服务器,并选择了Go作为编程语言。这当然不仅仅是因为Go语言与我们的既定产品名称有着异曲同工之妙——但这样的效果确实不错——同时也主要考虑到Go语言惊人的性能表现以及原生内置并发能力,这二者正是打造一款高性能消息代理方案的必要前提。我们也很喜爱Go语言那简洁而完整的标准库,这使得利用Go编写应用程序时几乎不可能闹出什么意外。最后,Go应用程序会被编译成单一的静态链接二进制格式,这一点也让我们极为赞赏——因为我们希望自己的成果能够轻松快速地被部署到大量服务器之上。

我们将自己的服务器方案定名为MQTTParty,这是在向Ruby编写的高人气HTTParty库致敬。

高可用性:负载均衡

2014年年底时,我们的开源MQTT代理方案已经基本成型,但一项重要的功能缺失就是无法以集群化方式运行多个代理实例。我们希望这套MQTT代理方案能够在起步阶段就拥有出色的可用性水平。

在理论状态下,Karma Go设备应该能够与MQTTParty集群中的服务器之间直接连接——具体来讲,该服务器不需要具备或者要求相关Karma Go提供任何信息。MQTTParty服务器需要能够在不丢失状态的情况下进行随意替换。为了达成这项目标,我们需要引入一套负载均衡机制,并找到将状态数据存储在MQTTParty集群之外的方法。

对Karma Go连接进行负载均衡会带来一些有趣的挑战:当Karama Go连接到MQTTParty时,它会开启一条TCP连接直至该Karma Go设备关闭或者转为独立运行模式。整个过程大约维持几个小时,这意味着负载均衡器必须拥有出色的智能水平,可以在MQTTParty服务器的整个可用周期之内对上述长时连接进行均衡调整。

一般来讲,在处理负载均衡工作时(常见于各类网站,比如大家现在正在浏览的这个),我们面对的主要是短时、无状态且只需要尽快接入后端服务器的HTTP连接。后端服务器并不会通过负载均衡器接回用户设备,而是直接连入互联网。在这类场景当中,round robin算法往往能带来不错的效果。

不过根据Karma Go的情况来看,我们需要的是一款能够处理长时、双向、状态化通信的负载均衡器。在这种情况下使用round robin算法就比较捉急了,因为它会将未经均衡处理的状态发送至后端;这意味着某些后端服务器会面临大量接入客户端,但另一些则几乎无人问津。

由于我们希望尽可能提高均衡处理水平,因此理想的负载均衡器应当立足于每一台后端服务器考量其能够承受的连接数量,并将新连接分配给当前连接承载数量最低的后端服务器。

除了在后端服务器连接数量上作出更好的优化,这同时意味着当故障发生时(例如某台后端服务器出现宕机),受到影响的各客户端应该通过重新连接被指向至仍然正常运转的后端服务器,同时尽可能降低剩余后端服务器的额外负载。

我们一般会利用Amazon Web Services的Elastic Load Balancer(简称ELB)处理各类负载均衡需求,但这一次其效果无法令人满意。尽管ELB的Cross Zone Load-Balancing在理论上应该能够处理好长时任务,但我们的性能测试显示随着时间推移,仍有相当一部分指向MQTTParty的连接并未经过正确的负载均衡处理。

事实证明,HAProxy即使面对高强度负载仍然拥有可靠的实际表现,而且在处理每台MQTTParty服务器的连接数量时也显得游刃有余。

出于这个原因,我们决定选择HAProxy作为负载均衡方案,并运用其leastconn balancing(即最小连接数目负载均衡)算法。我们将PROXY协议同send-proxy指令配合使用,从而允许后端MQTTParty服务器能够通过该负载均衡器识别出接入客户端的IP地址。

事实证明,HAProxy即使面对高强度负载仍然拥有可靠的实际表现,而且在处理每台MQTTParty服务器的连接数量时也显得游刃有余。

可扩展性:无状态服务器与MQTTProxy

为了尽可能提高MQTTParty集群的可用性水平,我们在集群的无状态方面下了一番工夫。当某款Karma Go设备接入到一台MQTTParty服务器时,该服务器将参考Karma Go的独特识别码在Redis中保存自己的一条识别码。而Karma Go每次向MQTTParty发送心跳包时,这条识别码都会在Redis当中得到更新。

这条信息将由一套自定义编写而成的MQTT/HTTP代理服务器——也就是MQTTProxy所使用,而该服务器将作为后端应用程序与接入MQTTParty的Karma Go设备之间的中介者存在。

当一款后端应用尝试向Karma Go发送消息时(例如通知其进行固件更新),即会通过HTTP面向MQTTProxy进行发送。MQTTProxy随后利用来自Redis的该Karma Go相关信息检测其当前是否已经接入某台MQTTParty服务器。如果已经接入,那么该消息会通过HTTP被传递至相关MQTTParty服务器,再由后者接力将消息通过MQTT交付至Karma Go应用。

具体实现
这一切听起来都很美好——但仅仅是理论层面上的美好,我们还需要确保设备体系能够真正处理好接入Karma Go设备所带来的大量负载。考虑到这一点,我们决定构建起一款“伪造Karma Go”客户端,并利用它模拟数万条接入运行状态下MQTTParty集群的连接。这些客户端会以模拟真实使用场景的方式,周期性发送大量MQTT消息。

我们利用Prometheus从这些测试客户端以及MQTTParty节点当中捕捉到了大量性能指标,并通过Grafana对其进行可视化转换。如此一来,我们就能提早识别并解决性能瓶颈,从而确保这套堆栈能在生产环境下为Karma Go提供强大的支撑。

总结
我们致力于通过Karma Go帮助客户能够随时随地接入网络、处理事务,而MQTTParty将帮助我们达到这项目标。我们期待着在未来与大家就更多软件堆栈方案交换意见,也希望各位能够在评论当中留下您的真知灼见。

回复

举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表