[翻译]MQTT要点系列第二部分-发布/订阅

原文

欢迎来到MQTT要点系列的第二部分,MQTT的核心功能和概念的技术博客系列。在该系列的第一篇博文中,我们介绍了MQTT协议以及其起源和历史。如果你还没看这部分,强烈建议应该先看看。

发布/订阅模式

发布/订阅(Pub/Sub)对于传统的客户端-服务器(C/S)模式,提供了另一种选择。在客户端-服务器模式中,客户端直接与一个服务端点通信。而在发布/订阅模式解偶了两种客户端,一种是称为发布者发送消息的客户端,一种是称为订阅者接收消息的客户端。发布者和订阅者不会直接联系,实际上他们不知道彼此是否存在,他们之间的连接是通过一个第三者组件,称为消息代理(Broker)。消息代理的工作是过滤所有进来的消息,然后正确地分发到订阅者。因此,让我们更深入地了解发布/订阅的一些方面(我们将在一分钟之内讨论一些MQTT规范)。

publish-subscribe

MQTT发布/订阅

发布/订阅最重要的一面是解偶了消息的发布者和接收(订阅者),这个解偶有几个维度:

  • 空间解偶: 发布者和订阅者不需要知道彼此(例如没有IP地址和端口的交换)。
  • 时间解偶: 发布者和订阅者不需要同时运行。
  • 同步解偶: 在发布或接收的期间,不需要终端两者上的操作。

综上所述,发布/订阅模式移除了消息的发布者和接收者/订阅者之间的直接连接。消息代理的过滤行为是的它可以控制哪个客户端/订阅者接收到哪个消息。解偶有三个维度:空间,时间和同步。

可扩展性

发布/订阅模式比传统的客户端-服务器方法更好扩展。因为消息代理上的操作可以高度并行化,并且消息可以事件驱动的方式处理。消息缓存以及消息的智能路由通常是提供可扩展性的决定性因素。尽管如此,扩展到百万的连接是一个挑战。如此高级别的连接数可以通过消息代理节点集群来完成,集群通过负载均衡器将负载分布到更多的单独服务器上(这个主题超出了当前文章的范围,我们将在另外的文件中来讨论)。

消息过滤

很显然,消息代理在发布/订阅的过程中充当着重要的角色。那消息是如何过滤所有消息,以使得每个订阅者只接收到感兴趣的消息的呢?如你所见,消息代理有以下几个过滤选项:

选项一: 基于主题过滤

该过滤基于每个消息的主题。接收客户端向消息代理订阅其感兴趣的主题,然后消息代理确保接收客户端获得所有发布到该主题的消息。通常,主题是具有层级结构的字符串,允许基于有限数量的表达式进行过滤。

选项二: 基于内容过滤

基于内容过滤中,消息代理基于一个特定的内容过滤语言来过滤消息。接收客户端向消息代理订阅他们感兴趣的过滤查询。这个方法的一个重要确定是必须事先知道消息的内容,以及消息不能被加密或容易修改。

选项三: 基于类型过滤

当使用面向对象的语言,基于消息(事件)的类型/类别的过滤是常见的做法。例如,一个订阅者可以监听异常类型或者任意子类型的所有消息。

当然,发布/订阅也不是每种用例的答案,在你使用这个模型之前,你需要考虑一些事情。发布者和订阅者的解偶是发布/订阅的关键,它本身就是一个挑战。例如,你需要事先知道发布的数据结构是什么样。对于基于主题的过滤,发布者和订阅者都需要知道使用哪个主题。另一个需要记住的是消息传递,发布者不能假设某人在正监听它发送的消息。在一些情况下,可能没有订阅者读取特定的消息。

MQTT

我们大体上探索了发布/订阅模式,现在让我们专注于MQTT。取决于你想要完成什么,MQTT体现了我们提到的发布/订阅的所有方面:

  • MQTT空间上解偶了发布者和订阅者。要分发或者接收消息,发布者和订阅者仅仅需要知道消息代理服务的主机名/IP地址以及端口。
  • MQTT通过时间解偶。虽然大部分MQTT用例都会近实时地传递消息,但如果需要,消息代理可以为不在线的客户端存储消息。(要存储消息,两个条件必须满足: 客户端以持久会进行连接的,以及订阅的主题服务质量大于0)。
  • MQTT以异步工作。因为大部分客户端库都是以异步工作,以及基于回调或者相似的模型,当等待消息或者发布消息时,任务不会被阻塞。在某些使用情况,同步也是需要和可取的,为了等待某个消息,一些库具有同步的API。但是流程通常是异步的。

应该提到的另一件事情是MQTT特别容易在客户端使用。大部分发布/订阅系统都有消息代理端的逻辑,但是当使用客户端库时,MQTT确实是发布/订阅的本质,这使得它成为小型和受限制设备的轻量级协议。

当MQTT使用基于主题的消息过滤时,每条消息都含有一个主题,消息代理使用主题来决定订阅的客户端是否收到消息。请阅读第五部分学习更多关于主题的概念。如果需要,也可以通过HiveMQ MQTT消息代理和我们的自定义插件系统来设置基于内容的过滤。

为了应对发布/订阅系统的挑战,MQTT具有服务质量级别(QoS)。你可以轻松制定消息从客户端成功传递到代理或者从代理传递到客户端,但是也有可能没有人订阅到特定的主题。如果这是一个问题,这取决于消息代理如何处理这样的情况。例如,HiveMQ的MQTT代理具有插件系统,它可以识别这样的情况。你可以让代理执行操作,或者简单地将每条消息记录到数据以进行历史分析。为了保持层级主题数的灵活性,非常仔细地设计主题树并为了将来的用例保留空间是很重要的。如果你遵循这些策略,MQTT非常适合生产设置。

与消息队列的区别

对于MQTT的名称和协议是否实现了一个消息队列,存在这混淆,我们将尝试阐明该主题并解释其中的差异。在上一篇文章中,我们提到了MQTT是参考了来自IBM的MQ系列产品,与“消息队列”无关。不管这个名字是来自哪里,理解MQTT和传统的消息队列之间的差异是有用的:

消息队列存储消息,直到这些消息被消费 - 当你使用消息队列,没一个进入的消息都被存储在一个队列里,直到消息被一个客户端(通常称为一个消费者)取走。在消息队列中,任何客户端都不可能处理消息,就像在MQTT中如果没有人订阅一个主题。

一个消息只可以被一个客户端消费 - 另一个大不同是,在传统的消息队列中,一个消息只能被一个消费者处理。负载在队列的所有消费者之间分配,在MQTT中行为恰好相反,每个订阅相同主题的订阅着都会得到消息。

队列是命名的,且必须明确创建 - 一个队列要比主题严格得多。在一个队列可以被使用前,必须使用单独的命令显式地创建队列。只有在队列被命名和创建之后,才可能发布或者消费消息。相反,MQTT的主题非常灵活,可以即时创建。

如果你能想到我们忽略的其他差异,我们很乐意在评论中听到你的意见。


该MQTT系列的第二部到这里结束了,下周我们将更深入地研究MQTT的客户端和代理的构成以及它们如何连接