REST是Roy Fielding博士在他的毕业论文中首先提出来的。Roy Fielding何许人也?他是HTTP协议的主要设计者,是Apache Web Server的作者,是Apache软件基金会的联合创始人。所以他的论文一问世,就引起了业界普遍的重视。在这篇论文里,他总结了Web,作为全世界最大的分布式系统,之所以能成功的经验,也就是REST架构风格。
时至今日,REST这个术语被广泛使用。要是你到现在还没听过REST,你可以去一边儿rest去了……不过,虽然REST这个术语被广泛使用,但是很多人对其的了解都是很模糊的。本文从REST架构风格的起源、REST的特点、REST实现要点等多个方面,来探讨一下,究竟REST为何物。
一、什么是架构风格
要了解“REST架构风格”,我们得先知道什么是"架构风格"。
说到风格,先来看看我们熟悉的服装风格:
是什么形成了这些服装风格,让人一眼就能分辨出它们呢?——这是因为这些服装的设计满足了一组设计约束。比如“韩版”色彩要清新、“嘻哈”造型要宽大、“朋克”要黑色加钉子质感。满足了这些约束,就会形成服装风格,同时也具备这些风格所带来的特点,比如“韩版”潮、“嘻哈”随性、“朋克”酷……
类似地,架构风格就是一组架构的约束,满足了这些约束,就可以具备一系列架构的属性,比如可伸缩性、简单性、可移植性等等。架构风格有很多种,Roy Fielding在他的论文里列举了二十多种。这里,我们列举对我们理解REST有帮助的几个架构风格:
客户-服务器风格(Client-Server,CS):这种风格是基于网络的应用最常见的一种架构风格。客户端发送请求给服务器,服务器处理请求并将结果返回给客户端。这种架构风格约束要求分离关注点。功能的适当分离会简化服务器组件,从而提高可伸缩性。这种分离也使得服务器和客户端可以独立演进。
分层系统风格(Layered System,LS):一个分层系统是按照层次来组织的,每一层为在其之上的层提供服务,层与层之间不能跨层访问(层隔离原则)。每层的关注点分离,这样使得层与层之间解耦,每层的组件可以独立演进。
不同的架构风格还可以组合在一起,从而生成新的风格。比如:
分层-客户-服务器风格(LayeredClient-Server,LCS):这种风格在CS风格的基础上添加了代理(proxy)组件层和网关(gateway)组件层。这些额外的中间层,为系统添加了负载均衡、安全性检查等功能。
客户-无状态-服务器风格(Client-Stateless-Server,CSS):这种风格在CS风格的基础添加了新的约束:服务器不允许有会话状态。从客户端发到服务器的每个请求必须包含理解请求所需的全部信息,不能用任何保存在服务器上的上下文信息。这个风格使得系统具有很好的可伸缩性,因为服务器不用保存多个请求之间的状态,请求可以分发到不同的服务器中进行处理。
缓存风格($):缓存个别请求的结果,以便可以被后面的请求重用。缓存风格可以提高性能,尤其是在基于网络的系统中。
按需代码风格(Code On Demand,COD):这种风格中,客户端向服务器发送请求,服务器会根据需要把相应的代码发给客户端,然后客户端在本地执行这些代码。举个例子,比如浏览器访问一个网站,网站会把js脚本发给浏览器,浏览器在本地执行这些js脚本,对网页进行渲染。按需代码的优点有:能够为一个已部署的客户端添加功能,改善了可扩展性和可配置性;当代码在本地与用户交互而不是通过远程交互时,能够得到更好的用户可觉察性能和效率;由于服务器将工作交给了客户端,从而改善了服务器的可伸缩性。它的缺点也很明显:它会更加复杂性;在系统安全性方面也面临挑战;如果客户端无法信任服务器,还会带来部署问题。
讲了这么多,究竟架构风格有什么用呢?
可以通过架构风格来比较分析各种不同架构的特点。因为一种具体架构既包含功能属性又包含非功能属性,直接比较不同类型系统的架构会比较困难。而架构风格通过忽略架构中其余部分的偶然性细节,捕获了一种交互模式的本质特征,更容易对不同架构进行对比分析。
可以指导具体架构设计。一种具体的架构其实就是特定风格的一个实例。软件架构设计要满足应用的一系列需求,而为满足这些需求,就要求架构应该必须具有一系列的属性(比如可伸缩性、网络效率等)。而架构风格对架构的约束,使得架构具备了相应的属性,可以对具体的架构设计提供指导。
可以推导出新的架构风格。通过将多种基本风格组合为互相协作的一个整体,可以推导出新的架构风格。这种新的架构风格会继承基础风格的一系列架构属性,形成自己独有的架构属性。进而可以用新的风格指导架构设计。
二、什么是REST架构风格
REST架构风格是Roy Fielding博士总结的Web架构背后的设计基本原理。它可以由一系列基础架构风格推导出来的。从空风格开始,通过逐步增加约束(组合基础架构风格),最终形成REST架构风格,其推导过程如下:
推导过程主要用到了我们上一节所列举的基础架构风格。综合这些基础架构风格的约束,总的来说,REST架构风格的约束主要有6个:
- 客户-服务器:通信只能由客户端单方面发起,表现为请求 - 响应的形式。
- 无状态:通信的会话状态(Session State)应该全部由客户端负责维护。
- 分层系统:通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
- 缓存:响应内容可以在通信链的某处被缓存,以改善网络效率。
- 统一接口:通信链的组件之间通过统一的接口相互通信,整体的架构得到了简化,交互的可见性也得到了改善。
- 按需代码(可选):支持通过下载并执行一些代码(例如 Java Applet、Flash 或 JavaScript),对客户端的功能进行扩展。
其中,强调组件之间要有一个统一的接口,是REST架构风格区别于其他基于网络的架构风格的核心特征。通过统一接口,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了 独立的可进化性。然而,付出的代价是,统一接口降低了效率,因为信息都使用标准化的形式来转移,而不能使用特定于应用需求的形式。
由于遵循了这些约束,REST架构风格就获得了相应的架构属性:
- 可伸缩性:通过客户-服务器、分层系统、无状态等约束,使得系统获得了很好的可伸缩性。
- 简单性:通过客户-服务器、分层系统、统一接口等约束,分离关注点,使得系统更容易被理解和实现。
- 可修改性:通过客户-服务器、分层系统、统一接口、按需代码等约束,使得系统获得了独立进化、动态扩展、可重用的能力。
- 网络效率:关于基于网络的应用的一个有趣现象是,最佳的应用性能就是不使用网络。通过缓存、按需代码等约束,使得系统可以尽可能减少网络访问,提升网络效率,减少由其他约束(如无状态、分层系统)引入的网络开销。
三、REST架构风格的一个实例——Web架构
从REST被提出的历史也可以看出,其实现代互联网Web架构——这个世界上最大最成功的分布式应用的架构——就是REST架构风格的一个具体实例。
构成Web架构的组件有:
- 用户代理:如Web浏览器。
- 来源服务器:如Apache httpd。是资源的权威来源。
- 代理:如Netscape代理。一个代理组件是由客户端选择的中间组件,用来为其他的服务、数据转换、性能增强、或安全保护提供接口封装。
- 网关:也叫反向代理,如nginx。一个网关组件是由网络或来源服务器强加的中间组件,用来为其他的服务、 数据转换、性能增强,或安全增强提供接口封装。代理和网关之间的区别是,何时使用代理是由客户端来决定的。
为什么这些组件组合起来可以形成REST架构风格呢?这离不开Web背后的三大基石:URI、HTTP、超媒体。
1. URI作为资源的标识
什么是资源?——任何能够被命名的信息都能够作为一个资源,比如一份文档、一张图片。一个资源是到一组实体概念上的映射,而不是实体本身。比如,“一份代码的最新版本”这个资源,它的值是在不断变化的;而“这份代码的V1.0版本”这个资源,它的值是静态的。这两个是截然不同的资源,即使某个时刻他们映射的实体是相同的(即V1.0版本就是最新版本)。URI就是这个资源的标识。
对资源的这一抽象的定义使得 Web 架构的核心功能得以实现。首先,它通过包含了很多信息来源而没有人为地通过类型或实现对它们加以区分,从而实现了通用性。其次,它允许引用到表述的延迟绑定,从而支持基于请求的性质来进行内容协商。最后,它允许一个创作者引用一个概念而不是引用此概念的某个单独的表述,从而使得当表述改变时无须修改所有的现有链接(假设创作者使用了正确的标识符)。
2. HTTP作为统一的接口
有了资源的抽象,就可以对资源的操作设定具有统一语义的接口,这就是HTTP的使命。HTTP的统一接口表现在几个方面:
统一的方法:
GET: 用于获取资源。它既是幂等的,又是安全无副作用的。
POST: 用于请求服务端创建一个资源。它即不是幂等的,也不是安全的。
PUT:用于客户端创建或重写一个资源。它是幂等的,但不是安全的。
DELETE:用于删除资源。它是幂等的,但不是安全的。统一的状态码,如:
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求统一的控制信息,如:
Header中的Cache-Control用来控制缓存策略
Header中的Connection用来控制是否持久连接
有了HTTP这套统一语义的接口,Web系统中的各个组件,可以不再关心具体的消息内容而工作。比如缓存服务器可以识别是GET方法,根据其即安全又幂等的特性,默认对其进行缓存;并且可以根据Cache-Control中指定的信息,来决定缓存的策略。这套统一语义的接口使得Web系统间组件相互解耦,整个系统的集成变得简单。
3. 超媒体作为资源的表述和应用状态的引擎
一个表述,指的是某个资源在某个特定时刻的视图。这个视图被编码为一种或多种可转移的格式,比如HTML、XML、JSON、MP3、JPEG等。对于一个资源的访问,总是通过其表述方式来间接完成的。
Web系统中以超媒体作为资源的表述。超媒体最大的特征是具有超链接。这一简单的特性,却可以使得客户和服务端得到的极大解耦。想象这样一个场景,你在淘宝上购买商品。这个过程是这样的:
你会发现,在这个过程中,你唯一需要知道的信息只有网站入口,而整个购买过程的顺利进行,完全依赖于超链接给你的提示,每进入一步,超链接都会提示你后续还能做哪些操作。你不必记住每个操作具体的API,只要你能明白超媒体的“语义”,你就能顺利完成购买。你需要记住的东西越少,你和网站之间的耦合就越弱。比如这个例子中,网站变更"将商品加入购物车的API“对你来说是透明的。
我们再来看看,这个过程中,网站的状态是如何变化的:
在这个过程中,超媒体实际上充当了网站(应用)状态迁移的引擎,它推动着整个应用状态的迁移——与传统状态机的不同之处在于,客户端预先不知道可能的状态和迁移,当应用到达一个新的状态时,接下来的状态迁移是可被发现的。就像是一个寻宝的过程。
再开一下脑洞,如果把整个互联网看成是一个分布式应用,而“你”是个客户端程序,那么只要你这个客户端能理解超媒体中的“语义”,就可以通过选择超链接的方式,将“超媒体作为应用状态的引擎”,探索式的完成各种业务流程。这里,你这个客户端和分布式应用是非常松耦合的。
小结
可以说,Web架构之所以能实现REST架构风格,就是因为上述三大基石。它们三个,作为粘合剂把整个系统的各个组件联在了一起;同时,作为整个系统的灵魂,使得Web系统具有了一系列优越性,成为世界上最大最成功的分布式应用。理解了上述Web的三大基石,也就可以理解为什么这个架构风格要被命名为REST(REpresentation State Transfer,表述性状态转移)这么古怪的名字了!它含义是:以超媒体作为资源的表述,通过HTTP将这些表述转移到客户端,而由客户端通过服务器在超媒体中提供的可选超链接,来控制服务器上应用状态的迁移。总结REST最为核心的就是:将超媒体作为应用状态的引擎(Hypermedia As The Engine Of Application State,缩写HATEOAS)。
四、把Web作为建造分布式系统的平台。
你当然可以自己按照REST架构风格的约束,重新设计一个特定的架构。然而,作为REST风格的亲生儿子,Web架构,已经是一个非常成熟的架构体系了,包含非常多高可用的中间件。基于Web来建造分布式系统,就相当于站在了巨人的肩膀上,不但可以享受Web强大而丰富的基础设施,还可以获得REST架构风格所带来的一系列架构优点。这何乐而不为呢?因此,现有的RESTful架构基本上都是基于Web的。
要想基于Web构建一个REST架构风格的系统,除了需要满足服务端无状态的约束外,最核心的就是统一接口的约束。这个“统一接口”也就是我们平常所说的RESTful API。这也是为什么我们在谈论REST架构实现的时候,很多时候实际上讨论的是RESTful API的设计。
然而很多人对RESTful API的内涵并不清楚,有的以为用HTTP传输就是RESTful API,有的以为用URL定义的API就是RESTful API……为了评判API满足REST架构风格约束要求的程度,Richardson提出了REST成熟度模型:
第0级:仅使用HTTP作为传输方式
其特征是整个服务只使用单个的URI,并且使用单个的HTTP方法(通常是POST)。比如基于SOAP规范的各种技术(WSDL、WS-Transfer等),它们仅使用HTTP来转移SOAP的载荷,完全忽略了HTTP其他部分。
优缺点:不能利用Web架构带来的任何益处。
第1级:使用URI描述资源
其使用了很多URI来标识资源,但是只使用单个HTTP动词。1级服务和0级服务的主要区别在于,1级服务暴露出了很多逻辑上的资源,而0级服务将所有的交互埋入了单个(大型的、复杂的)资源。在1级服务中,通常会将操作名嵌入到URI中。如http://example.com/get_users
或者http://example.com/users?method=get
优缺点:不同的URI可以对资源进行建模。但是由于只用一个HTTP动词(一般是POST),无法利用Web的缓存等中间件。
第2级:使用统一语意的HTTP动词和状态码
其使用了大量URI标识资源,同时使用多个统一语义HTTP动词来操作资源:GET(读取)、POST(创建)、PUT(更新)、DELETE(删除),并且使用HTTP状态码来协调交互。其中,合理使用GET具有重要的意义。因为GET默认是幂等并且安全的操作,这使得Web系统中的中间节点可以使用缓存机制缓存GET的结果,该机制是让目前Web运转如此良好的关键因素之一。
优缺点:可以利用Web的大部分基础设施。由于HTTP动词对操作的幂等性和安全性做了严格的区分,准确使用这些动词会使得系统更具健壮性。
第3级:使用超媒体作为状态转移引擎
在第2级的基础上,此级服务支持“超媒体作为应用状态引擎”的理念。在表述中除了包含请求的数据,还包含消费者下一步可能感兴趣的其他资源URI链接。超媒体控制的关键在于它告诉客户端下一步能够做什么。
优缺点:可以在保证客户端不受影响的条件下,改变服务接口(通过超媒体返回的链接),实现了客户端和服务端的松耦合。
当然,客户端要想理解返回的URI代表什么意思,还需要理解其“语义”。可以通过构建语义网络,使得客户端根据返回的信息推断出它的语义,这种方法比较复杂但可以使得客户端和服务器足够的解耦;也可以通和客户端事先约定好,这种方法相对简单,只是客户端需要知道一些带外信息,耦合性稍微高一点。
你们现在的RESTful API成熟度达到了第几级呢?——就我观察,我厂的API大部分还处在第1级,或者第1级到第2级的路上……
小结
以Web为基础,构建REST服务,最重要的是RESTful API的设计。只有完全达到REST成熟度第2级,才能很好的利用Web架构提供的基础设施;只有完全达到第3级,实现超媒体控制,才能享受REST架构风格带来的好处,构建出像Web一样成功的分布式服务。(注:还有一个前提是,服务需要满足无状态的约束。)
五、REST架构的问题
虽然REST架构具有一系列优点,但是它也不是万能的,也有其自身的缺点:
1. 统一接口带来的性能损失:由于信息都使用标准化的形式来转移,而不能使用特定于应用需求的形式,网络效率上会比较低。
2. 缓存的存在,使得一致性问题更为突出:缓存机制可以弥补一些网络效率。但这有引入了新问题,客户端获得的数据可能是缓存了的旧数据,而服务器又没有主动通知缓存更新的机制,这使得分布式系统的一致性问题更加突出。
除此之外,由于目前REST的主要实现是基于HTTP的,HTTP的一些缺点也导致了一些问题:
3. 请求响应式交互,使得服务端无法通知客户端:HTTP的设计使得服务端不具备向客户端发起通知的能力。客户端要想获得最新状态,需要不停的向服务端发起请求。这既浪费网络带宽,也无法满足高时效性的需求。
4. HTTP动词表达力还不足够:目前的HTTP动词只能支持基本的CRUD操作。并且某些情况下,用GET来获取资源还受到URL长度的限制。用这些动词描述完整的服务有时会有些力不从心。目前已有一些提案,给HTTP增加一些动词,比如:SEARCH(搜索)、INCLUDE(将资源加入到某个资源集合中并返回服务端设定的 URI)、PLACE(使用客户端指定的 URI 向资源集合中添加资源)、MERGE(通过提供的表述合并部分资源)等等。
5. HTTP明文传输带来的安全性问题:HTTP使用明文传输,并且不验证报文的完整性,使得报文很容被篡改。而全栈使用HTTPS来解决安全性问题,又会带来较高的性能开销。
还是那句经典:没有银弹。架构设计的过程就是不停地权衡利弊得失,从中寻求一个最佳的平衡点的过程。REST对于有些场景可能是很好的,它的这些缺点影响并不大;而对于另一些应用场景,它的缺点可能就是不可接受的。因此,在实际架构设计过程中,是否使用REST架构,还是要具体问题具体分析,因地制宜。
参考: