学习REST时会有一些疑问,为什么要引入无状态?为什么错误响应不能使用200状态码?为什么URI要体现资源? 为什么要统一使用HTTP方法?
其实上面这些规定来自REST的约束,也就是设计REST接口应该遵循的一些规则,REST的约束则是为了实现REST的目标而推导出来的,而满足HATEOAS才是成熟的RESTful API。
REST
REST全称表述性状态转移(Representational State Transfer), 出自Roy Fielding的《架构风格与基于网络的软件架构设计》。URL, HTTP和HTML是互联网架构的三大web技术,这三大技术背后的两大核心理念是:资源(resource)和表述(representation)。
- 资源
资源实际上是"信息资源",它们的本质形式都是一串比特数据流。在Web上,将一个事物赋以URL,它就会成为一个资源。
- 表述
表述是信息资源(状态、数据或标记)的一种封装,使用诸如XML,JSON或HTML这样的格式进行编码。资源可以有一个或多个表述,客户端和服务器使用媒体类型来标识给接收方(客户端或服务器)的表述类型。
表述性状态转移可以这样理解,把后端资源的状态转移到浏览器中,如用户是否登录,账户有多少钱。从客户端的角度看,它并不关心资源是什么,因为它从来看不到资源,它看到的永远只是URL和表述。
REST约束
客户端-服务器 (Client-Server)
客户端-服务器是最基本的约束,以客户端-服务器形式建立一个基本的分布式架构,从而支持客户端逻辑与服务端逻辑独立演化。客户端处理js交互,CSS, HTML渲染等,服务器处理业务逻辑,数据存储。客户端可以升级某个版本,而不影响服务器的功能。
无状态 (Stateless)
无状态要求服务端维护资源状态,例如文件的状态,报表的状态。客户端维护会话状态,当前访问到了哪个目录,报表的那一页。客户端的每个请求应当包含所有必要信息,服务器处理每个请求后,应该返回会话状态给客户端。
网络调用和本地调用不一样,谁来维护客户端和服务器之间的会话状态是个重要的问题。例如,在NFS中服务器会记录客户端当前会话访问到了哪个目录,在客户端不是很多的情况下,服务端可以承担维护会话状态的工作。在互联网场景下,会存在大量客户端访问,例如天猫的双11秒杀活动,一台服务端处理不过来,那么增加多个服务端。假如服务端保存会话状态,那么增加服务端后也带来了问题,服务器之间需要同步会话状态。如果让客户端保存会话状态,服务器之间就无需同步会话状态,服务器就可以做到横向扩展。
缓存 (Cacheability)
缓存约束建立在 客户端-服务器 和 无状态 之上。客户端、服务器或者中间件可以缓存重用之前的响应。服务器给客户机的响应,被显示的标注为可缓存和不可缓存从而减少部分网络交互,提高用户的体验。具体一点就是,浏览器中缓存CSS文件,CDN可以缓存某张图片,这样不但减轻了服务器的负载,还提高了响应速度。
统一接口 (Uniform Interface)
统一接口指所有的客户端和服务器必须共用一套接口约束。接口约束与业务上下文无关,必须足够抽象,满足不同服务和客户端重用的要求。统一接口由三大元素构成:资源标识符语法、方法和媒体类型。
- 资源标识符语法——数据传输的来源地和目的地是什么?
资源对象通过URL来指定,对于 http://www.example.com/customer/c111 这个URL地址,http://www.example.com/customer 指向具体的服务,c111代表客户记录ID。
- 方法——传输数据所使用的协议机制是什么?
操作的动作,使用GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS等HTTP方法。操作的结果是什么,用200,400,503等状态码表示。
- 媒体类型——传输的数据是何种类型?
媒体类型用于指定方法可处理的数据类型。HTTP使用的最常见行业媒体类型由IANA注册。HTTP头中使用Content-Type指定数据类型。
Content-Type: application/json
分层系统 (Layered System)
分层约束建立在客户端-服务器约束之上,可以向架构中添加中间件,每一层都可以独立的部署和进化。常见的中间件有,负载均衡设备(F5, Nginx, LVS),缓存组件(Redis, memcache),消息队列(kafka, rabbitmq)。
按需代码 (Code-On-Demand, 可选)
按需代码属于可选约束,允许客户端的逻辑独立于服务器进行更新。这一层通常基于虚拟机实现,浏览器上的java script虚拟机。浏览器上可以执行applet, JavaScript或脚本语言。每次交互都和服务器进行交互,网络延迟会体验会很差,设计服务时将部分逻辑放到浏览器上执行。
REST目标
性能 (Performance)
性能可分为网络性能、用户感知的性能、网络效率。不可靠网络会导致消息的丢失,乱序,延迟,往往需要重试来解决。靠近数据存储端处理数据,消息数量和网络往返次数越少,系统开销就越小,使用缓存可以保证数据就近处理。
可伸缩性 (Scalability)
REST通过分层系统和统一接口等约束支持跨服务实例的负载均衡。通过无状态约束将客户端和服务端的交互变成相互独立的请求-响应。请求不需要发送到同一个服务实例,服务器之间也不需要同步会话状态,会话状态包含在每个请求里,从而使之变得更简单。大多数REST约束的重点在于支持横向扩展,增加服务器的数量,以及根据请求数量来调整资源的消耗。
简单性 (Simplicity)
分布式架构的理想设计是通过分离功能降低复杂性,每个单元的功能不同且容易理解。统一接口使得架构中服务、消费者和中间件之间使用相同的消息类型。应用无状态,使每个请求无需参考同一会话中早前送来的消息,凭借自身就能被理解。客户机-服务器和分层系统约束,是服务逻辑、消费者逻辑和中间件逻辑只需要通过明确的技术锲约即可理解。
可修改性 (Modifiability)
随着时间的推移,需求会改变,技术的架构的调整也是不可避免的。对于大型分布式系统,改善架构时难以重新部署整个系统,可修改性尤其重要。可修改性可进一步修改为以下部分:
演进能力 —— 重构和重新部署一个服务、消费者或中间件组件,不会影响架构其他部分。
扩展能力 —— 向架构中添加功能。
定制能力 —— 暂时修改解决方案的一部分以执行特殊任务的能力。
配置能力 —— 永久改变架构某一部分的能力。
重用能力 —— 向架构中添加新的解决方案、重用现有的服务、中间件、方法和媒体类型,而无需修改的能力。
可视性 (Visibility)
可视性指在同一架构中的一部分能够监控和调节其他部分之间的交互,当协议是可见的,缓存、代理、防火墙等组件就可以监视甚至参与其中。常见的做法是建立中间件服务代理,服务代理作为监控客户端和服务器之间通信的中介,并对请求做监控。
REST主要通过统一接口约束来支持可视性,服务代理可以从消息中提取出通用的信息。中间件根据HTTP状态码就可以判断请求是否成功,而不需要读取消息体中的业务状态码。这就是REST要求错误的响应不能使用200 OK状态码的原因,也是统一接口约束的意义所在。资源的操作语义必须由HTTP消息体之前的部分完全表达,不能将操作语义封装在HTTP消息体内部。这样做是为了提高交互的可见性,以便于通信链的中间组件实现缓存、安全审计等等功能。
可移植性 (Portability)
可靠性指服务和解决方案可以方便地从一个平台移植到另一个平台。按需代码约束对这一目标的支持是实现执行环境的标准化,只要客户机有JavaScript虚拟机,就可以执行JavaScript代码。
可靠性 (Reliability)
分布式架构的可靠性取决于解决方案和服务,通过避免单点失败,采用失效备援机制,依赖动态预测和响应失败情况的监测功能,架构的可靠性得以改善。
HATEOAS
HATEOAS出自Fielding 博士论文中的一句话"Hypermedia As The Engine Of Application State",即将超媒体作为应用状态的引擎,又叫超文本驱动。Web应用可以看成由很多状态组成的有限状态机。服务器通过超媒体暴露出提供的资源,客户端在解析超媒体发现服务器可以提供的资源。资源之间通过超链接关联起来。超媒体不仅包含数据,还包含状态迁移的语义,超媒体做为引擎驱动web应用状态的迁移。与传统状态机不同之处在于无法预知可能的状态和迁移,当应用到达一个新状态,接下来可能的那些状态迁移是被发现的。
浏览器打开新浪http://www.sina.com.cn/时,解释HTML后给用户呈现出”新闻”,“体育,“财经”,“军事”,“科技”等板块,这些板块对应的应该是一个超链接。用户点击“体育”板块,浏览器通过相应的超链接得到体育资讯的表述,这是超文本驱动的一个例子。
服务成熟度
2008年的Qcon会议上,有人给Web服务成熟度做了划分。划分的模型基于服务对URI, HTTP和超媒体的支持。
- 零级服务
服务成熟度中最基本的一级,特征为服务有单个URI,并且使用HTTP方法(通常是POST)。例如:某个web服务对外仅提供一个url地址访问,客户端和服务器通过POST交互,数据格式为json格式,具体资源名称,操作动作,请求参数分别在body的某个字段中。
POST /v1/tmall/notify
{
"Version": "2.0",
"RequestID": "16050648374068377",
"Timestamp": "2021-11-11 11:20:37",
"Charset": "GBK",
"Method": "send",
"Signature": "B78D08CAB531BE24BBEB315119C70EAA",
"IsPerfReq": "false",
}
- 一级服务
一级服务使用了很多URI,但只使用单个HTTP动词,和零级服务的区别是一级服务暴露出了很多逻辑上的资源,零级服务将所有的交互埋入了单个资源。一级服务中,通过将操作名称和参数插入URI中,然后将URI传递给远程服务,操作被隐藏起来了。
POST /v1/promotion/list
POST /v1/promotion/create
POST /v1/promotion/update/123
POST /v1/promotion/delete/123
- 二级服务
二级服务使用了大量可通过URI寻址的资源,并且支持多个HTTP动词对资源做增删改查。
GET /v1/promotions
POST /v1/promotions
PUT /v1/promotion/123
PATCH /v1/promotion/123
DELETE /v1/promotion/123
- 三级服务
最web感知的服务级别,支持HATEOAS。表述包含了消费者可能感兴趣的其他资源的URI链接。
curl -v -X GET https://api-m.sandbox.paypal.com/v1/catalogs/products?page_size=2&page=1&total_required=true \
-H "Content-Type: application/json" \
-H "Authorization: Bearer Access-Token"
{
"total_items": 20,
"total_pages": 10,
"products": [
{
"id": "72255d4849af8ed6e0df1173",
"name": "Video Streaming Service",
"description": "Video streaming service",
"create_time": "2018-12-10T21:20:49Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/catalogs/products/72255d4849af8ed6e0df1173",
"rel": "self",
"method": "GET"
}
]
},
{
"id": "PROD-XYAB12ABSB7868434",
"name": "Video Streaming Service",
"description": "Audio streaming service",
"create_time": "2018-12-10T21:20:49Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/catalogs/products/125d4849af8ed6e0df18",
"rel": "self",
"method": "GET"
}
]
}
],
"links": [
{
"href": "https://api-m.paypal.com/v1/catalogs/products?page_size=2&page=1",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.paypal.com/v1/catalogs/products?page_size=2&page=2",
"rel": "next",
"method": "GET"
},
{
"href": "https://api-m.paypal.com/v1/catalogs/products?page_size=2&page=10",
"rel": "last",
"method": "GET"
}
]
}
总结
REST架构的目标为性能、可伸缩性、简单性、可修改性、可视性、可移植性、可靠性。为了实现这些架构目标,推导出了六大约束:客户端-服务器、无状态、缓存、统一接口、分层系统、按需代码。REST是一种网络架构风格,并不是一种严格的标准。实际上,HTTP是为REST而生的,也可以说REST是世界上最成功的分布式应用架构风格。
参考
- 架构风格与基于网络的软件架构设计
- https://www.infoq.cn/article/understanding-restful-style
- https://www.infoq.cn/article/doctor-fielding-article-review
- https://www.infoq.cn/article/how-to-design-a-good-restful-api
- https://developer.paypal.com/docs/api/catalog-products/v1/
- SOA与REST:用REST构建企业级SOA解决方案
- RESTful Web APIs
- REST in Pratice