基于 HTTP/1.1 协议, 设计一个REST-like的RPC协议(上)

REST本身是风格,不是规范

对于 REST 本身,以及它的四级的 Uniform interface 的介绍,遍地开花,本文不作冗余的介绍。 较为权威,但不一定公正的描述,可以通过前面的链接直达 Wikipedia。

REST只是一种实现风格,不是一个强制规范,更连连RFC规范都算不上。 ( RFC7231 作为参考文献,提到了一下 )

由此,REST的历史可以追述到2000年,也就是那篇很多人听过,但可能很多人没读过的 博士论文 ,它来自Roy Thomas Fielding。

2000年,离现在也有段时日了,如今REST怎么就突然走红了呢?几个正常人都能够想到的因素是:它借助了日益「完善的HTTP协议」、丰富的工具链,网络的日益发达。

对于HTTP协议,最权威的描述,莫过于RFC对其制定的规范了,有兴趣的可以去这里看下我对它的 简要概览

可以说,REST对于HTTP协议的理解是比一般人要深入的,也使得URL的设计产生了「设计感」。

REST的优势

丰富的工具与运行时环境

例如,cURL (最好的「全能」URL处理命令行工具) , POSTMAN (最好的GUI调试HTTP工具) , NGINX (最有希望成为市场第一的HTTP Server) , Apache HTTP Server (稳居HTTP Server排名第一的常青树)

由于HTTP协议,还能够非常好的通过部署SSL证书,实现HTTPS协议,REST的安全性也能够得到一定的提升,比如不安全链路下的内容防篡改。

无状态

这看上去是REST的一个优势,但使用REST的大多数服务,都是有状态的。无状态的设计,则是把对状态管理的责任,甩给了第三方平台。

也就是说,无状态,更多的是对RPC通信协议的「无状态化设计」。

服务对于无状态化的处理,大致有三种方式:

  1. 状态信息通过请求,也就是入参传递
  2. 状态信息通过外置的数据源存储,可能是缓存,也可能是数据库
  3. 托管给运行时环境的容器,例如J2EE应用服务器集群

Uniform interface

这一点,是REST最只管,最富设计感的特性,它可以使得一个URL表达一个客观对象,使得URL富有现实含义。

并且能够借助于,HTTP 协议的4个基本Method对这个对象的状态进行维护。

最常见、入门级的例子,是对于 Order , 或是 Book ,再或是 Article 这样的实体进行维护,比如以下四个HTTP Method:

  • POST : https://yanjing.wang/articles/
  • GET : https://yanjing.wang/articles/design-an-rest-like-rpc-over-http11
  • DELETE : https://yanjing.wang/article/design-an-rest-like-rpc-over-http11
  • PUT : https://yanjing.wang/article/design-an-rest-like-rpc-over-http11

URL一致性,使得这些URL看上去「几乎完美」,它及表达了目标对象的所述域,也表达了分类,还为目标对象分配了唯一性的标识。

REST的不足

不支持分布式事务

关于REST的绝大多数文章里,都没有设计「分布式事务」,这个复杂、难解的问题。

对于「分布式事务」,我的一个观点是——这是个伪命题。分布式事务并不存在,只存在本地事务。

在分布式环境下,保障多个点数据一致的算法,有Paxos以及Raft。

而要保障多个对一个事件所进行的操作都是「一致的」,则需要协调器的参与。 这里的「一致的」,所指的并不一定是保证两个被操作数据的一致性,而是「同目标」。

例如,对一篇 Article 进行 POST 动作时,常见的,还会更新这篇 Article 所标注的 TagsCategory 索引。 更新 TagsCategory 则是同目标,不同结果的两个子操作。

在复杂场景下,对 Article , Tags , Category 的维护,可能分属于三个独立的系统。 这需要有一个协调器进行分布式协调,才能保障一篇新的 Article 被正确发布,保障文章与两个索引的一致性。

使用场景的自述型

HTTP协议是被广泛使用的,它经常出现在普通人的视野中,普通人可能不了解HTTP所代表的全称,但在记忆网址,也就是域名的时候,不会忘记HTTP这个「符号」。

那么,一个HTTP地址,所代表的,是一个可被浏览器访问,并能够通过浏览器渲染出一个Web页面的地址,还是一个仅仅传输JSON格式报文的API地址呢?

很显然,类似 https://yanjing.wang/articles/design-an-rest-like-rpc-over-http11 这样的地址,难以被分辨。 一般都需要通过浏览器进行尝试,或者通过阅读配套的文档才能够了解其使用场景。

在传统的软件工程项目中,在一个域名下的Application,经常会既提供用于浏览器访问的Page URL,也提供用于浏览器请求后端数据的API URL,这使得URL的设计更为复杂。

另外常见的做法,是为Page以及数据提供不同的访问域名,例如用域名 https://api.yanjiong.wang/v1 专门提供API服务,用域名 https://yanjong.wang 专门提供Page服务。

这些应用实践方法,都会为软件工程带来更多的复杂性,更高的集成难度。

报文的承载能力

需要注意的是,一些HTTP方法,是不支持 Request Body 进行数据传输的。

虽然,作为客户端,可以在准备报文时,准备好 Request Body 进行发送,但RFC规范,则对这种情况作出规定——服务端可以忽略这些不应该出现的 Request Body

为人熟知的HTTP Method,就是 GET , 它并不支持客户端发送 Request Body , 而仅仅能够从 Request URL 中传输请求数据。

不太为人熟知的 DELETE 也与 GET 类似,不支持 Request Body

复杂场景下的RPC协议与报文的要求

URL要求

  1. 需要能够秉承 REST 风格的优势,也就是 Uniform interface
  2. 能够直观的区分两次不同的请求,
  3. 既能够表达对客观单个实体的操作,也能够表达对于一组实体的「事务性操作」

报文要求

  1. 以JSON为基础,在报文体较大时,不解析报文体保障性能。
  2. 能够提供相关扩展机制,对参数进行非JSON的序列化
  3. 能够快速的进行「回滚」,也就是撤销动作

追踪要求

  1. 能够在分布式条件下,对请求的调用链路进行跨系统无缝跟踪
  2. 能够区分首次与重试不同的请求
  3. 便于日志记录,还原故障现场的上下文环境

版本要求

  1. 能够区分不同的版本便于路由
  2. 能够由客户端指定兼容版本区间

查询要求

  1. 提供处理过程中的状态查询能力
  2. 提供处理结果的查询能力
  3. 能够实时展示调用进度

安全要求

  1. 提供报文传输安全性保护能力
  2. 提供双向身份认证能力
  3. 流量控制
  4. 应急隔离