本节主要讲述发布/订阅模式。首先关注发布/订阅的基本特征,然后再聚焦到MQTT本身,着重讲述MQTT协议与传统消息队列协议的不同。
一、发布/订阅模式发布/订阅模式即Pub/Sub,是传统客户端/服务器模型(客户端直接连接服务器)的替代。传统的客户端/服务器模型是客户端直接连接到服务端(Endpoint),而发布/订阅模式实现了客户端的解耦。客户端(Publisher,消息发布者)发送特定的消息到另一个客户端(Subscriber,消息接收者)。这意味着发布者和订阅者都无需关心对方的存在与否。它们之间还有第三个组件,即消息经纪人(Broker),所有的发布者和接收者都要连接到消息经纪人,消息经纪人会过滤所有到来的消息,并根据需要分发这些消息。
发布/订阅模式实现了发布者和订阅者之间的解耦,可以从多维度进行区分:
1)空间解耦:发布者和订阅者均无需知道彼此的存在(比如对方的IP地址和端口)
2)时间解耦:发布者和订阅者无需同时运行
3)同步解耦:在发布或接收期间,双方组件的操作不会暂停
总的来说,发布/订阅模式从消息上对发布者和订阅者进行了解耦,并且通过对消息的过滤实现了只有某些客户端才能受到相应的消息。解耦包含了三个维度:空间、时间、同步。
发布/订阅模式还提供了比传统的客户端-服务器模式更好的可扩展性。这是因为,在经纪人Broker上的操作,是可以高度并行和基于事件驱动进行处理的。而且还可以对消息进行缓存和对消息进行智能路由,以进一步改善可扩展性。但是,要把发布/订阅模式扩展到支持数百万的连接,在技术上仍然是一个挑战。这一点可以通过对经纪人Broker节点进行集群来实现,以便通过负载平衡器把负载分配到更多的服务器节点来实现。
消息过滤经纪人Broker怎样过滤所有的消息,以及怎样让每个订阅者只获得他感兴趣的消息。
选项1:基于主题的过滤过滤是根据标题(Subject)或主题(Topic)的,而标题或主题是每一个消息的组成部分之一。正在接收的客户端订阅了它感兴趣的主题Topic,就可以从经纪人Broker那里得到它基于订阅主题Topic的所有消息。主题Topic通常是一个具有分层结构的字符串,可以基于有限数量的表达式进行过滤。
选项2:基于内容的过滤基于内容的过滤正如名字的暗示,是指经纪人Broker过滤基于特定内容、特定过滤器语言的消息。因此客户端订阅他们感兴趣的消息的过滤查询。这种过滤方法有一个较大的缺点,即消息的内容必须事先已知,并且消息不容易进行加密或修改。
选项3:基于类型的过滤在使用面向对象的编程语言时,有一种常见的做法,基于消息或事件的类型/类别进行过滤。在这种情况下,订阅者可以监听所有的消息,并从类型异常或子类型中获取消息。
当然,发布/订阅模式并不能解决一切,在使用此模式之前需要考虑一些事情。发布者和订阅者之间的解耦是发布/订阅模式的关键,这给使用它带来了一些挑战。你必须事先知道发布的数据的结构。在基于主题的过滤的场景,无论是发布者还是订阅者,都需要理解怎样正确的使用主题。另一方面是消息的交付,发布者不能想当然地认为有人会监听他发送的消息。因为在一些情况下,发布的消息可能没有任何一位订阅者。
现在我们对发布/订阅模式已经有了一定的了解,但对于MQTT还不够。MQTT体现了前面所有提及的方面,这取决于你想达到什么目标。MQTT从空间上对发布者和订阅者进行了解耦。因此,客户端只需知道Broker的主机名(或IP)和端口,以实现发布/订阅消息。MQTT还从时间上对发布者和订阅者进行了解耦,但这往往是倒退的行为,因为大多数使用场景都是以近实时的方式传递消息。当然,Broker还要能够存储信息,方便那些不在线的客户端(订阅者)。(这需要满足两个条件:客户端已连接一次,它的会话是持久的;并且客户端订阅了服务质量QoS大于0的主题,关于QoS在后续会讲到)。
MQTT还能够解耦同步,因为大多数客户端库是异步工作的,是基于回调或类似模型的实现。所以客户端在等待消息或发布消息时不会阻止其它任务的执行。但也有一些场景需要使用同步,而且这也是可以实现的。因此有些客户端库提供了同步API,以等待某种消息。但通常的流程都是异步的。还应该特别强调一点,MQTT在客户端特别容易使用。大多数的发布/订阅系统的实现逻辑通常都在Broker端,而且MQTT协议仅仅使用了发布/订阅模式的基础内容,这使得MQTT确实是轻量级协议,确实是为小型和受限的设备而设计的协议。