系统间通信:消息队列技术
现在介绍系统间通信及传输方案 : 消息队列。首先会讨论消息 队列协议的基本原理和工作方式,并介绍 几种目前生产环境中常用的消息协议: MQTT、 XMPP、 Stomp, AMQP, OpenWire 等。然后在这个基础上介绍一款消息队列产品 : ActiveMQ, 并通过一 个虚拟的业务场景向读者展示如何将消息队列使用到业务环境中最后提到另 一款消息 队列产品, Apache Kafka,它也是现在业务系统中应用广泛的消息队列软件 。
消息队列原理
消息
消息队列技术中(后文也简称 MQ )有一个基本概念需要在开篇前进行讨论:消息和消息 协议。 消息即是信息的载体,这个描述相信我们都能够明白。消息发送者需要知道如何构 造消息 :消息接收者需要知道如何解析消息 。 所以为了让消息发送者和消息接收者都能够明白 消息所承载的信息,消息本身就需要按照一种统一的格式描述消息,这种统一的格式称之为 消息协议 。 有效的消息一定具有某一种格式,而没有格式的消息是没有意义的 。
消息从发送者到接收者的方式也有两种。一种我们可以称为直接消息通信,也就是说消 从一端发出后(消息发送者)直接发送消息给最终接收者,并得到处理后的回执一一无论是阻 塞方式还是非阻塞方式。这种通信方式的一种具体实现就是我们已经介绍过的 RPC; 另一种方式可以称为中转消息通信 ,即 消息从某一端发出后,首先会进入一个消息代理进行处理和临时存储,当消息接收者准备好或者达到某种传输条件后,再由消息代理发送给另一端 ,这种方式的一种具体实现就是消息队列。
服务结构
无论是RPC还是 MQ,它们的网络通信基础都建立在网络 I/O 模型之 上。 先进的网络 I/O 模型将赋予 MQ协议优异的性能表现,当然性能也不仅仅取决于网络 I/O 模型。
从下图可以看到,某一种消息通信组件(或者叫作程序库)的实现都建立在某种“消息协议”基础上,例如 gRPC是一款 Google推出的 RPC组件,其主要使用 HTTP2协议作为消息的描述格式,也支持原始的 HTTP 协议: RabbitMQ 消息通信组件属于一种 MQ 通信的实现, 它主要支持 AMQP 消息格式协议,通过安装第三方插件还可以让它支持 MQTT等消息协议: ActiveMQ 支持多种消息格式也属于一种 MQ 通信实现,例如 AMQP 协议、 STOMP 协议和 MQTT 协议
虽然消息协议存在“私有协议”和“开放协议”之分(这主要看协议本身是否向行业开放 规范文档,是否允许组织或者个人对协议进行实现),虽然某一个软件(程序库)不一定只支 持一种协议,虽然某一种协议也不一定只 由一种软件(程序库)实现,但是它们都适用消息通 信组件都要基于一种或多种消息协议才能工作这个基本规则。
消息协议
要讨论 MQ, 就应该从这些 MQ 支持的消息协议开始讨论 。下面我们了解几种消息协议,它们是 XMPP、 Stomp, MQTT 和 AMQP。
XMPP 协议
XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议。在此基础上,XMPP协议已经被用来构建大规模即时通信系统、游戏平台、协作空间及语音和视频会议系统。
XMPP由几个小的构造块组成,并在此基础上扩展出了更多的构造块。XMPP中有众多系统:发布-订阅服务、多人聊天、表单检索与处理、服务发现、实时数据传输、隐私处理及远程过程调用等。
与其他协议一样,XMPP定义了在两个或者更多通信实体间传递数据所采用的格式。对于XMPP,实体通常是指客户端服务器,但是其也允许客户端与客户端或服务器端与服务器端的通信。
在XMPP上交换的是XML数据,采用这种格式,使XMPP协议获得了极大的可扩展性,因为使用XML可以方便的新增功能并保证前后向兼容。使用XML较二进制协议占用更大的带宽,但获得的优势是具有了几乎无限的可扩展性。
在XMPP中,XML数据被组织为了一对流,每个流分别对应通信的一个方向。每个XML流均由一个开始元素、后跟XMPP节和其他顶级元素,然后是一个结束元素组成。每个XMPP节(可带有子元素及属性)均是该流的一级子元素。在XMPP连接末尾,这两个流形成了一对有效的XMPP文档。
XMPP节构成了该协议的核心部分,而XMPP应用程序则关注如何发送和响应各种类型的节。节可能包含网络上其他实体的信息、类似于电子邮件的个人消息或为计算机处理而设计的结构化数据。
XMPP网络
任何XMPP网络都是由若干角色组成,可以分为服务器端、客户端、组件和服务器插件。
XMPP网络与WWW网络及EMAIL网络不同,XMPP服务器之间寻址只会跳一次,而EMAIL协议则会有多个中转服务器,XMPP保存完整的列表。
服务器
XMPP服务器是任何XMPP网络的通信系统,服务器的任务就是为XMPP节提供路由。无论这些节是从内部的一个用户发往另外一个用户还是本地用户发送给服务器。
一组能够相互通信的XMPP服务器构成了XMPP网络。
MPP服务器总是允许用户连接到自己,但是也可以编写直接使用服务器-服务器协议的应用和程序,来减轻路由消耗。
客户端
大多数XMPP实体均是客户端,通过客户端-服务器协议连接到XMPP服务器。
客户端必须向某个地方的XMPP服务器进行身份验证。服务器会将该客户端发送的所有节路由到合适的目的地。
服务器还负责管理客户端会话的其他几个方面,包括花名册及裸地址。
组件
不仅仅是客户端能够连接到XMPP服务器,大多数服务器还支持外部服务器组件。这些组件通过添加某种新服务来增强服务器的行为。这些组件在服务器内有各自的身份和地址,但运行在外部并通过组件协议通信。
组件协议(XEP-0114)可以让开发人员以一种服务器不可知的方式创建服务器扩展,例如多人聊天服务。
组件也需要向XMPP服务器进行身份验证,但要较客户端的完全SASL验证简单,例如口令。
每个组件编程服务器内部一个可单独寻址的实体,在外界看类似于一个子服务器。除了基本节之外,XMPP服务器不会代替已连接组件来管理其他节的路由。
服务器还允许组件在内部自行路由或管理节,因而更为灵活。
插件
许多XMPP服务器还支持使用插件进行扩展,但插件深入到服务器内部,有较高的效率以及最低的通用性。插件一般是绑定特定类型的服务器的。
Stomp协议
Stomp 协议,英文全名 Streaming Text Orientated Message Protocol,中文名称为流文本定 向消息协议。是一种以纯文本为载体的协议(以文本为载体的意思是它 的消息格式规范中没有 类似 XMPP 协议那样的 xrnl 格式要求 , 你可以将它看作吨结 构化数据”)。
一个标准的 Stomp 协议包括以下部分: 命令/信息关键字、 头信息、 文本内容。如下图所示:
它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互,类似于OpenWire(一种二进制协议)。
由于其设计简单,很容易开发客户端,因此在多种语言和多种平台上得到广泛应用。其中最流行的STOMP消息代理是Apache ActiveMQ。
STOMP协议工作于TCP协议之上,使用了下列命令:
-
CONNECT/STOMP
连接到 Stomp 代理端,如果使用 STOMP命令,那么 Stomp代理端的版本必须是 1.2
-
CONNECTED 信息
当 Stomp 代 理端收到客户端发送来的 Connect 命令并且处理成 功 后,将向这个客户端返回 CONNECTED 状态信息:如果这个过程中出现任何问 题 , 还可能返回 ERROR信息。
-
SEND 发送
客户端使用 SEND 命令,向某个指定位置 (代理端上的一个虚拟路 径)发送内容。 这样在这个路径上订阅了消息事件的其他客户端,将能够收到这个消息。
-
SUBSCRIBE 订阅
客户端使用 SUBSC阳BE订阅命令,向 Stomp服务代理订阅某 一个虚拟路径上的监听 。 这样当其他客户端使用 SEND 命令发送内 容到这个路径上时, 这个客户端就可以收到这个消息内容。 在使用 SUBSC阳BE 时,有一个重要的 ACK 属 性。 这个 ACK属性说明了 Stomp服务代理端发送给这个客户端的消息是否需要等待收到一个 ACK命令,才认为这个消息处理成功了。
-
UNSUBSCRIBE 退订
客户端使用这个命令,取消对某个路径上消息事件的监听 。 如果客户端给出的路径之前就没有被这个客户端订阅 ,那么这个命令执行无效。
-
BEGIN 开始
Stomp协议支持事务模式,在这种模式下,使用 Send命令从某 个客户端发出的消息,在没有使用 COMMIT 命令正式提交前 , 这些消息不会真正发送 给 Stomp 代理端 。 BEGIN 命令就是用于开启事务。注意, 一个事务中可以有 一条消息, 也可以有多条消息 。
-
MESSAGE 信息
当客户端在某个订阅的位置收到消息时,这个消息将通过 MESSAGE 关键字进行描述。
-
COMMIT 提交
当完成事务中的信息定义后,使用该命令提交事务 。 只有使用 COMMIT 命令后,在某一个事务中的一条或者多条消息才会进入 Stomp 代理端的队列, 订阅 了事件的其他客户端才能收到这些消息。
-
ABORT 取消
用于取消/终止当前还没有执行 COMMIT 命令的事务。
-
ACK 确认
当客户端使用 SUBSC阳BE 命令进行订阅时,如果在 SUBSC阳BE 命 令 中制定 ACK 属性为 client, 那么这个客户端在收到某条消息 CID 为× ×××)后,必 须向 Stomp 代理端发送 ACK 命令,这样代理端才会认为消息处理成功了;如果 Stomp客户端在断开连接之前都没有发送 ACK 命令,那么 Stomp 代理端将在这个客户端断开 连接后,将这条消息发送给其他客户端。
-
DISCONNECT 断开
断开 Stomp客户端与 Stomp代理端的连接
Stomp 协议中有两个重要的角色: Stomp 客户端与任意 Stomp 消息代理(Broker)。如下图所示:
MQTT协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。下图显示了MQTT架构:
设计规范
由于物联网的环境是非常特别的,所以MQTT遵循以下设计原则:
- 精简,不添加可有可无的功能;
- 发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递;
- 允许用户动态创建主题,零运维成本;
- 把传输量降到最低以提高传输效率;
- 把低带宽、高延迟、不稳定的网络等因素考虑在内;
- 支持连续的会话控制;
- 理解客户端计算能力可能很低;
- 提供服务质量管理;
- 假设数据不可知,不强求传输数据的类型与格式,保持灵活性。
主要特性
MQTT协议工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
-
使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。这一点很类似于XMPP,但是MQTT的信息冗余远小于XMPP,,因为XMPP使用XML格式文本来传递数据。
-
对负载内容屏蔽的消息传输。
-
使用TCP/IP提供网络连接
主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。
-
有三种消息发布服务质量
“至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。这一种方式主要普通APP的推送,倘若你的智能设备在消息推送时未联网,推送过去没收到,再次联网也就收不到了。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别。在计费系统中,消息重复或丢失会导致不正确的结果。这种最高质量的消息发布服务还可以用于即时通讯类的APP的推送,确保用户收到且只会收到一次。
-
小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量。
-
使用Last Will和Testament特性通知有关各方客户端异常中断的机制。
Last Will:即遗言机制,用于通知同一主题下的其他设备发送遗言的设备已经断开了连接。
Testament:遗嘱机制,功能类似于Last Will。
MQTT协议原理
MQTT协议实现方式:
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
- Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)。
- payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
网络传输与应用消息:
MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流的双向传输。当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。
MQTT客户端
一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:
- 发布其他客户端可能会订阅的信息;
- 订阅其它客户端发布的消息;
- 退订或删除应用程序的消息;
- 断开与服务器连接;
MQTT服务器
MQTT服务器以称为"消息代理"(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:
- 接受来自客户的网络连接;
- 接受客户发布的应用信息;
- 处理来自客户端的订阅和退订请求;
- 向订阅的客户转发应用程序消息。
MQTT协议中的订阅、主题、会话:
-
订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
-
会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
-
主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
-
主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
-
负载(Payload)
消息订阅者所具体接收的内容。
MQTT协议中的方法
MQTT协议中定义了一些方法(也被称为动作),来于表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:
- Connect 等待与服务器建立连接;
- Disconnect MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话;
- Subscribe 订阅;
- UnSubscribe 取消客户端的一个或多个topics订阅;
- Publish MQTT客户端发送消息请求,发送完成后返回应用程序线程。
MQTT协议数据包结构
在MQTT协议中,一个MQTT数据包由:固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。MQTT数据包结构如下:
-
固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
-
可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
MQTT数据包中包含一个可变头,它驻位于固定的头和负载之间。可变头的内容因数据包类型而不同,较常的应用是作为包的标识:很多类型数据包中都包括一个2字节的数据包标识字段,这些类型的包有:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。
-
消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。
Payload消息体位MQTT数据包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息:
- CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码;
- SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS;
- SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复;
- UNSUBSCRIBE,消息体内容是要订阅的主题。
AMQP 协议
AMQP 是什么:
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议。
AMQP 协议要比 Stomp 协议复杂得多, 作为一种网络通信协议, AMQP 工作在七 层/五层网络模型的应用层,是一个典型的应用层协议; 另外,由于 AMQP 协议存在多种元素 定义,且这些元素定义工作在不同的领域 。 例如 Channel 的定义是为了基于网络连接记录会话状 态: Queue等元素帮助 AMQP完成路由规则,这些元素在 Message消息记录中都需要有所体现。下图是AMQP模型: