目录
介绍
Roy Fielding在他的论文中将REST(Representational State Transfer)定义为“分布式超媒体系统的架构风格”,将其定义为具有统一接口的分层代码——客户端——高速缓存——无状态服务器。在Web上可用的描述此风格的文章总是在“RESTful Web服务”的上下文中定义REST,其中HTTP用于操纵无数的URL可寻址资源。但是在真正考虑之后,人们意识到RESTful Web服务只是REST作为一种架构风格的应用,而且风格本身具有更深,更简单,隐藏的性质,我将在这里尝试发现。 文章描述了这一风格,可在“RESTful web 服务”的上下文中在Web总是定义在REST上查阅,其中http被用来操纵一系列URL地址资源。但是,在真正考虑了它之后,人们意识到RESTful Web服务只是作为一种体系结构风格的REST应用程序,而且风格本身具有更深、更简单、隐藏的特性,我将在这里尝试揭示它。
REST的核心
为了揭示具有代表性的状态转移风格的隐藏性,让我们向后剖析其名称。
单词转移(transfer)意味着至少有两个进程通过某种媒介进行通信,这意味着一个分布式系统。
单词状态(state)意味着分布式系统的一个进程将其内部“周围世界的视图”转移到另一个进程。这个“世界的内部视图”是该进程履行职责所需的所有相关信息(见图1)。它包含从环境中收集的信息和内部生成的信息,并由名词(nouns)表示。
图1.嵌入其环境并包含“周围世界的内部视图”的进程。
“代表性(representational)”这个词意味着进程不会真正发送他们的“世界内部视图”,而是将其编码为接收者可理解的描述(表示)。表示隐藏了进程内部状态的内部性质(实现)。
状态(State)与命令(Commands)
转移状态与转移命令不同,因为它并不意味着接收者的任何具体行为。如果发送方传输命令(如在RPC的情况下),它负责接收方的行为,但如果发送方传输状态,它只是发出当前世界视图(它观察到的)的信号,并让接收方执行任何它认为需要的行动(见图2)。这意味着发送方假定对发送的状态有某种反应,但不要求具体行动将此决定留给接收方。这导致进程之间的控制分布和耦合的松动。
图2. RPC(顶部)与REST(底部)通信风格。
为了实现成功的通讯,需要从收件人的角度完成转移的“世界视图”。这意味着每条消息都需要是自包含的,携带足够的信息,以便独立于之前或之后发送的任何其他消息进行处理。反过来,这意味着无上下文交互,因为任何通信过程都不需要维护交互上下文(如预期的消息序列号)(我将使用“无上下文”而不是“无状态”来避免混淆)。
阀门(Valve)示例
为了澄清所说的内容,让我们使用通过通信链路连接到SCADA系统(图3)的电子控制阀的示例。阀门可以放置在0(完全关闭)或100(完全打开)之间的任何位置。
图3.连接到SCADA系统的电子控制阀。
电子阀门控制器通过通信链路(图4中的消息1,3,4,5)周期性地发送单字节消息,表示当前阀门位置。它还接受表示目标阀门位置的单字节消息(图4中的消息2和5)。
图4. RESTful进程之间的示例消息序列——未经请求的消息模型。
包含阀门位置的单字节是阀门控制器的完整“世界内部视图”。当控制器发送消息时,它不会假设来自SCADA系统的任何特定动作,它只是通告其状态。当SCADA系统发送消息时,它不会采取任何特定的动作,因为控制器可以自由地采取它认为可行的任何动作(尽管SCADA系统肯定期望物理阀最终处于期望的位置)。在图4中,控制器接受它接收的状态并适当地移动阀门,定期通知阀门的新当前位置。
RESTful风格同样适用于图5所示的请求——响应模型。
图5. RESTful进程之间的示例消息序列——请求——响应模型。
REST最重要的含义是,由于SCADA系统没有线索,因此阀门控制器有责任了解如何从一种状态转移到另一种状态。SCADA系统可能甚至是盲目的,不能处理从控制器接收的消息。它可能只是周期性地发送它自己的“世界内部视图”(图4中的消息2和5),假设这实际上是唯一的“真实事实”并且控制器需要处理这个事实。
相比之下,图6显示了以RPC风格进行通信的相同进程,其中SCADA系统完全负责明确管理控制器的内部状态。
图6.通过RPC进行通信的进程之间的示例消息序列图。
安全性和等幂性
该示例说明了另外两个REST规则。第一个指出,读取或接收其他进程的状态是安全操作,这意味着它不会触发 该进程的可见状态的改变。可见是由系统业务逻辑执行的与该进程的任何后续交互可观察到的状态。安全操作可以通过例如生成日志条目来改变远程进程的不可见状态。第二个规则是将状态发送到远程进程是幂等操作,这意味着发送相同的状态不止一次会产生相同的效果,就好像状态只发送一次一样(注意图4中的消息5不会产生任何影响)。这与RPC风格形成对比,后者不会强加这种行为。值得一提的是,根据定义,所有安全操作都是幂等的。
REST =简单事务
总结到目前为止所写的内容,RESTful协议由传递与消息接收者相关的发送者的完整状态的消息组成,表示为相互可理解的表示,并且每个消息由接收者以安全或幂等的方式处理。除了松散耦合之外,这种方法的优点是实现事务操作的简单性和效率。
读事务
当连续读取包含在一个“会话”中的远程进程状态部分时,无论此状态的并发更新如何,都会返回状态的一致视图,从而实现事务性RPC风格的读取。常见的解决方案是,首先通过发送专用消息来启动远程组件上的事务,该消息使其生成其内部状态的快照,然后通过一系列部分请求读取快照,并且在最后使用两阶段或三阶段的事务提交完成转换(见图7)。这种方法会产生通信和资源开销。如果整个相关状态在单个消息中传输,则可以完全避免整个问题(参见图7底部)。在这种情况下,经典的锁定或队列机制足以有效地序列化操作。
图7. RPC(顶部)与RESTful(底部)读取。在RESTful方法中,可以以更加资源有效的方式推迟并发状态修改。
写事务
与事务读取相同,RPC风格事务性写入最终需要与两阶段或三阶段事务提交进行简单对话(参见图8顶部)。相反,可以使用单个请求消息执行RESTful写入事务(参见图8底部)。当所有相关状态在单个消息中传输时,锁定或队列机制足以有效地序列化操作。此外,如果事务由于某种原因失败(请求消息丢失,确认丢失等),发送者可以简单地重新发送消息,因为接收者的行为是幂等的。
图8 RPC(顶部)vs RESTful(底部)写入。在RESTful方法中,可以以更加资源有效的方式推迟并发状态修改。
状态修补
在单个消息中发送整个状态是最简单和最优选的解决方案,但由于需要通过线路发送大量数据而常常效率低下。当状态的修改部分与整个状态相比较小时,低效率尤其明显。为了解决这个问题,需要实现状态分区机制。如果状态的块是唯一可识别的(例如,通过名称或索引),则仅需要传输对状态的改变(补丁)。
为了说明这一点,我们假设我们的电子阀门控制器控制的不仅仅是一千个阀门,SCADA系统以请求——响应的方式与之通信。SCADA系统发送包含阀号和控制器响应的请求,其矢量为<阀门编号,位置>对。控制器还接受来自SCADA系统的成对矢量,并相应地设定指定阀门的位置(见图9)。这样,SCADA系统只能读取或写入它感兴趣的阀门位置。
图9.使用命名的状态部分进行状态修补
状态修补可能以前面描述的方式进行事务处理。唯一的要求是在单个消息中传输完整的补丁集以利用幂等性。
状态范围变更
在两个阀门示例中,仅使用了固定大小的状态,但RESTful方法也适用于可变大小的状态用例。图10显示了PC和音乐播放器之间的信息交换。PC首先要求整个播放器的状态包含三首歌曲。之后,PC向其发送一个新状态(由用户修改),其中Song2丢失,歌曲4和5是附加的。为了响应这些,播放器通过下载歌曲4和5以及删除歌曲2将其内部状态调整为由接收的表示描述的内部状态。
图10.可变大小状态用例。
此示例很好地说明了进程的内部状态与此状态的表示之间的区别。音乐播放器的内部状态是一组音频文件,但其表示是一组歌曲名称。
修补变量大小状态
如果歌曲集很小但播放器示例工作正常,但如果歌曲数量增加到数千,则每次将整个标题集合发送回来并返回可能是低效的。取而代之的是,只能发送PC和玩家状态之间的差异。引入描述NEW,MODIFIED和MISSING歌曲的元数据解决了这个问题(见图11)。值得注意的是,NEW,MODIFIED和MISSING标签是描述状态块的“及时性”的形容词,而不是命令收件人添加,更新或删除的动词。
图11.使用名词的变量状态修补。
PC以类似于diff UNIX实用程序描述文本文件中修改集的方式发送一条消息,描述音乐播放器状态的差异。写消息是自包含的,可以以幂等方式处理。
结论
总而言之,REST的核心思想 是以对话上下文无关的方式将“世界内部观”的表示作为名词在自包含消息中传递,以便接收者能够以幂等的方式处理它们,状态修补用形容词表示为优化 (见图12)。与RPC(其使用名词表示数据,动词表示操作)相比,优点是控制的分布,松散耦合和简化的事务处理。RESTful通信的缺点是(通常)更多的带宽消耗和更多的接收者负担,接收者有责任推断出适当的动作序列以适应当前的内部状态到新的预期状态。
图12. REST规则。
本文最重要的结论是,REST作为一种架构风格并不依赖于特定的通信协议。它可以,例如在中间件级通过消息传递服务实现,在应用级通过SNMP实现,在传输级通过普通TCP或UDP实现,或者甚至在物理层通过RS232实现。最流行的(事实上不是很准确)是通过称为“restful web services”的 HTTP协议实现的。我让读者将本文中描述的概念映射到HTTP协议中。