云巴:基于MQTT协议的实时通讯编程模型

概要

有人常问,云巴实时通讯系统到底提供了一种怎样的服务,与其余提供推送或 IM 服务的厂商有何本质区别。其实,从技术角度分析,云巴与其它同类厂商都是面向开发者的通讯服务,宏观的编程模型都是大同小异,真正差别则聚焦于产品定位,业务模式,基础技术水平等诸多细节上。本文暂不讨论具体产品形态上的差别,着重从技术角度浅谈实时通讯的编程模型。html

什么是实时通讯

「实时」(realtime) 一词在语义层面上隐含着对时间的约束(real-time constraint),在工程上,咱们习惯对「须要在必定时间内」 完成的操做称为「实时操做」。一般,实时可细分为 「软实时」(soft realtime),「准实时」(firm realtime)和 「硬实时」(hard realtime)。它们之间的差别,简单来讲,就是对没法在指定时间区间内(deadline)完成事务的容忍程度。维基百科上对这三者有以下解释前端

  • Hard – missing a deadline is a total system failure.git

  • Firm – infrequent deadline misses are tolerable, but may degrade the system's quality of service. The usefulness of a result is zero after its deadline.github

  • Soft – the usefulness of a result degrades after its deadline, thereby degrading the system's quality of service.算法

假如咱们把没法按时完成任务(missing a deadline)称为异常事件,那么硬实时系统没法容忍异常事件;准实时系统则可容忍极少许的异常事件,但超过必定数量后系统可用性为 0;软实时系统可容忍异常事件,可是每发生一次异常事件,系统可用性下降。编程

综上所述,咱们能够举例:后端

  • 火星上的无人探测器是硬实时系统,由于一次异常事件就极有可能致使探测器不可用,同理可类推核电站的监控系统,军用无人机系统,远程导弹的导航系统等一系列军工产品;设计模式

  • 金融交易系统是准实时系统,此类系统可容忍极少数的交易故障,一旦故障次数增长,系统就会陷入崩溃状态;缓存

  • 短信 / 手机推送 / 电商购物等都是软实时系统。对于此类系统,用户均可以容忍异常事件,可是太多的异常事件则会大幅下降系统可用程度,用户体验急剧下滑。安全

就目前来讲,绝大多数互联网产品(甚至能够说是 100%)都是软实时系统。云巴实时通讯系统的目标则是要作一个高可用的软实时系统

一个最简单的实时通讯编程模型

在软件工程中,不少复杂的项目其实均可以用一个很是简洁的模型来归纳。正如爱因斯坦所说的:「一切都应该尽量地简单,但不要太简单」(Everything should be made as simple as possible, but not simpler)。虽然这是描述物理世界的经验之谈,但一样适用于计算机领域,将物理世界的关系投射到某种人为语言(物理公式/计算机编程语言),其规律其实都是共通的。

让咱们假设这么一个简单的场景:对 10 个客户端发送一条消息

这个需求其实能够用伪码表示为:

for (i..10) {
    send_message(get_socket(i))
}

若是下图所示:

pic

在这个简单的需求下,咱们只须要让这 10 个客户端分别跟服务器创建 TCP 链接(本文暂时只讨论 TCP 协议),而后遍历地发送消息便可。显而易见,这是一个 O(N) 复杂度的逻辑。

基于这个简单的模型,咱们能够认为一条消息从发出到接收,有如下几个延时:

  • 网络延迟 ,通常是一个较为稳定的值,好比从北京到深圳,ping 延迟大约为 40 ms 左右;

  • 系统处理延迟,较之网络延迟,该值变化幅度较大,且可能因处理请求数的增长而急剧增大;

云巴实时通讯系统以 200 ms 延迟做为总延迟标准,也就是说,假如网络链路是从北京到深圳,除去网络延迟的 40 ms,要想达到 200 ms 的通讯时间,系统延迟必须小于 160 ms。

能够想象,当客户端数量达到必定数量级(好比百万级别)时,以上系统模型的实时性将面临极其严峻的考验。

分而治之

在海量用户下保持稳定的实时性,其实不少时候就只有一个手段:分而治之

图 1 表示的是单机处理状况。当单机的处理能力,带宽都没法应对客户端数量急剧增长的时候,咱们就必须将线路进行分割。并且图 1 只体现了推送的意图(单向),但通讯每每是一个双向的概念,综上,咱们将 图 1 改为下面的 图 2

pic

这样每台机器就能够处理符合其当前水位的链接。

在现实开发中,咱们可能不只仅知足于一个如此简单的消息系统,咱们可能想要有离线消息,数据统计,数据缓存,限流等一系列操做,因此咱们还能够再优化一下架构:

  • 将总体架构划分红业务逻辑层和数据存储层;

  • 数据存储层又能够根据存储数据类型的不一样来进一步划分;

  • 前端能够单独划分一个网络接入层;

  • 数据包的流向能够用 MQ 来串联;

这样咱们能够获得如下的图 3:

在这个模型中,网络接入层和消息业务逻辑层总体上应该是一个 stateless 的模块,能够较为轻松地作横行扩展。存储层做为一个有状态的模块,想要作到横行扩展是一件很不容易的事情。若是撇开这点来看,至此,这个模型理论上在应对海量用户的场景下应该是有效的。

通讯协议和技术栈的选择

作一个消息系统,不可避免地要涉及到对通讯协议的选择。咱们在对通讯协议的选择上,遵循如下几个原则:

  • 协议尽量精简轻量,由于在系统设计之初咱们就考虑了对物联网的支持,省电,节约流量都是目标之一;

  • 通用性好,扩展性强,方便后期作特性开发;

  • 协议在业界被普遍承认,且尽量多的有不一样语言的开源实现,以方便不一样技术栈的客户作集成;

综上,咱们没有从新自定义一份通讯协议,而是选择了基于长链接MQTT。从不少角度来看,MQTT 很是适合作消息总线的通讯协议,并且协议栈也足够轻巧和易于实现。云巴实时消息系统传输的消息体积较小(通常小于 4 KB),好比控制信号,普通聊天信息等。就这点上,针对物联网设计的 MQTT 有着自然的优点。后面,在不断地研究中咱们又发现,MQTT 其实不只仅适用于物联网场景,在不少要求低延迟高稳定性的非物联网场景也一样适用(好比手机端 app 推送,IM,直播弹幕等)。

从前面几个章节咱们看到,云巴消息系统是一个典型的 IO 密集型系统。在出于开发效率和稳定的考虑下,咱们选了 Erlang/OTP 做为主力开发语言。Erlang/OTP 做为一门小众开发语言(不管是国内仍是国际),在应付这类 IO 密集型系统上,有着得天独厚的优点(可参考 RabbitMQ 这个基于 Erlang/OTP 的著名开源项目):

  • 基于 actor 的进程建立模型,能够为每一个数据包建立一个 Erlang 处理进程,充分利用多核;

  • OTP 的开发框架抽象了分布式开发的许多细节,使得开发者在很小的心智负担下就能轻松快速地开发出功能原型;

  • Erlang/OTP 充分运用了容错思想,应对异常不是防,而是容,不少时候咱们写出一些安全逻辑上有漏洞的代码,在 Erlang/OTP 上竟然也能工做得好好的;

随着不断深刻地使用 Erlang/OTP, 其性能问题也渐渐凸显出来。咱们发现,当客户端请求量增长的时候,用 Erlang/OTP 写出的模块垂手可得地就能够将 CPU 跑满,从而让当前实例超负荷运转。不少时候出于成本上的考量,咱们没法选择更多核数的机器来提高 Erlang 虚拟机运行的性能(此点未明确验证过),因此只好选择适度增长服务处理实例来缓解压力。

不过,经过对业务模块更细粒度的划分,咱们能够将一些核心的小模块用 C/C++ 语言改写,在必定范围的复杂度内,能够有效提高总体处理性能。这也是咱们接下来优化核心系统的思路之一。

MQTT 的 Pub/Sub 模型与高可用 KV 存储

MQTT 协议采用的是 Pub/Sub 的编程模型。其中有三个比较关键的动做:publishsubscribeunsubsribe。经过前面几个章节的讨论,咱们又能够获得这么一个场景:

假如存在一个订阅量巨大的 topic(百万级),如何在单次 publish 中保证明时性 ?

其实,解决思路跟以前的场景是一致的:分而治之。咱们必须经过某种策略对 topic 进行分片,而后将分片分发到不一样的 publish 模块上进行处理。在必定的算法复杂度下,这个问题理论上是能够被有效解决的。因而,topic 的分片策略就成了高性能 publish 的关键。其实,若是想采用 MQTT 作海量消息系统,订阅关系的管理必定是没法绕开的大问题。它主要有如下几个设计难点:

  • 若是采用 KV 方式存储,如何设计数据结构 ?同上,咱们要怎样去设计一种高效的 topic 分片存储策略;

  • 订阅关系的管理是 MQTT 消息系统的核心模块,假如这个存储模块失效,就一定会致使消息通讯失败,从而让客户端收不到消息,这就必需要求这个模块必定是高可用的,也就意味着咱们必须构建一个高可用的 KV 存储集群,该集群要能容忍必定程度的节点失效;

  • 冷热 topic 要有淘汰机制,要有必定策略将不活跃的 topic 按期淘汰到磁盘以节约内存容量;

  • KV 存储集群要能高效地动态扩容;

在很长一段时间的实践中,咱们采用过好几种 KV 存储的集群方案,踩了很多坑,最后仍是决定本身造轮子来开发一个高可用的 KV 存储模块。不过这又是一个很大的话题,咱们将在后续博客中具体阐述咱们的作法。

缺陷与不足

在团队发展初期,因为人力和时间等种种因素,咱们把业务逻辑模块开发成了一个巨大的单体架构应用。在团队规模较小的状况下,单体架构的应用确实较好维护和开发,但随着新人的加入,单体架构则严重制约着特性开发和性能优化。从架构层面上来看,合理地划分更细粒度的模块,在性能和可维护性上采用微服务(microservice)设计模式,成了咱们将来优化系统的方向之一。

总结

软件工程上有「没有银弹」(No Silver Bullet)这条金科玉律,用户选择云服务商亦是如此,绝对没有完美的第三方云服务商,每一家均可能存在明显的优势和缺陷。用户必须从本身应用场景和痛点出发,选择合适的后端服务。云巴将会在本身产品的核心竞争力上持续发力,精打细磨,吸收行业内的高效实践经验,打造出更加优秀的高可用实时通讯系统。

相关文章
相关标签/搜索