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

HTTP/1.1 协议的潜力

RFC对于HTTP/1.1的规范,主要以 RFC2616 为基础,在 RFC7231 , RFC5789 进行了补充说明。

HTTP/1.1 协议,主要有9大方法,其中主要考察的有:

  • POST 写操作
  • PUT 写操作
  • PATCH 写操作
  • DELETE 写操作
  • HEAD 读操作
  • GET 读操作
  • TRACE 读操作(但可能会产生副作用)

另外的 CONNECT 方法主要处理与 HTTP Proxy 相关的事务 , OPTIONS 方法用于对协议内容的协商。

在2014年,RFC通过 RFC7230-7236 对RFC2616进了「Replace」。 不过RFC2616应用实在是过于广泛,即便是被宣布「Dead」,运行在全世界各地的协议实现,仍然是主流。

幂等性考量

幂等的概念是,对于相同的请求,被处理任意次,对数据的操作结果,等同于处理一次。

满足幂等性要求的Method,一般都是对于数据操作安全的。对于读操作,显然是满足幂等性要求的。

  • PUT
  • DELETE
  • HEAD
  • GET

上述四个方法,HTTP/1.1 要求协以幂等的方式进行实现。其中 HEAD 以及是 GET 是明显的读操作,而 PUTDELETE 是写操作。

在实现层面,对于幂等性的实现,都要求由请求方提供唯一性标识,也经常称之为「流水号」。它的生成方法,可以参见之前写的这篇文章:标识与唯一标识概览

当然,标识也可以通过请求方提供的多个唯一性要素,类似以「联合主键」的方式,在服务端进行计算和判定一致性。

承载性考量

HTTP Header

一般来说,Header部分的数据,都用来传输元数据。Header部分的数据结构,都以 Key-Value 的形式出现。

对于复杂的Value,也就是Value中再嵌套数据结构,可以采用「;」对齐进一步 Key-Value 化。

常见的 Request Header有:

  • User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
  • Refer : https://www.google.com/

User-Agent: 用于申明客户端的身份,其本意是用于声明「用户操作行为的代理者」。

Refer: 用于申明引用的地址,也就是本次请求源于上一次请求的地址。在分布式架构下,这个字段本质上是可以很好地用于声明一个请求链路的「上一跳地址」。

常见的 Response Header有:

  • Status Code200
  • ServerSimpleHTTP/0.6 Python/2.7.10
  • Date : Mon, 19 Dec 2016 14:16:20 GMT
  • ETag : W/"5715dcfb-47e8"
  • Expires : W/"5715dcfb-47e8"
  • Content-Type : application/json; charset=UTF-8

Status Code :是最为熟悉的响应头了。诸如 401 , 403 , 404 , 500 , 503 等等响应码,RFC都对其进行了「业务化的定义」。

Server : 默认都是以应用服务器的提供者和版本作为响应值。很多人都会把 Server 的概念等同于 Machine ,这是不正确的。

Date :指的是这一响应结果,在``Server``的响应时间,Client 可以对这些数据进行加以利用。

ETag :则是对响应体进行的摘要Hash计算,设计的初衷是为了让 Client 降低比较多个响应结果的差异,提高性能。

Expires :同样是提醒客户端,这一响应结果的有效期是多少,客户端可以自己根据过期时间,对所辖的缓存进行处理,或者出发其他时间。

Content-Type : 方便 Client 对响应体进行反序列化的解析操作。

HTTP Body

HTTP Body 并不是想象中的那样,只有 GET 方法不能够支持请求体的传输。

7个方法的 Request Body 支持情况如下:

  • POST 支持
  • PUT 支持
  • PATCH 支持
  • DELETE 不支持
  • HEAD 不支持
  • GET 不支持
  • TRACE 不支持

需要注意的是,DELETE 方法并不支持请求体的传输。

再看一下7个方法对于 Response Body 的支持情况:

  • POST 支持
  • PUT 支持
  • PATCH 支持
  • DELETE 支持
  • HEAD 不支持
  • GET 支持
  • TRACE 支持

相比之下,Response Body的支持情况,只有 HEAD 方法不支持了。

整体而言,对于用户贡献内容的行为(User Generate Content),HTTP/1.1 协议明显有着不平等的倾向性。

也就是说,只有 POST , PUT , PATCH 三个方法支持用户侧「提供内容」。

现如今,任何的一个互联网平台,都以 UGC 为核心,以用户提供的 内容 作为企业产品的生产资料,HTTP/1.1是不是有些落后了呢?

另外,作为程序员,不了解 PUT 以及 PATCH 这两个幂等Method的正确方法,并且在前端生成「唯一性标识」,没有成熟框架的条件下,选择大面积使用 POST 方法,似乎也得到了一个看似合理的解释。

提交-补偿模式的处理分布式事务的协调方案

对于分布式事务,一共有三种典型的协调方案,分别是:

  • 提交 (Commit) - 补偿 (Compensation), C-C模式
  • 尝试 (Try) - 确认(Confirm) / 取消(Cancel), T-C/C模式
  • 事件溯源 (Event Sourcing), ES模式

此处,对 C-C 进行一个极为简单的概述。

C-C 模式,也就是对数据进行正向操作的「反操作」,是在同步业务场景下,最简单的模式。

分布式事务的参与者的模型,是 1:n , 也就是分属于不同服务的 1 个消费者( Client )与 n 个提供者( Server ), 共同完成一次协调过程。

在消费者内部,需要内置一个「协调器」。 每次协调过程,则对于一个「协调处理单」的实体概念。

消费者,每次在进行「协调请求」前,除了需要准备好请求内容之外,还要准备好生成一个「协调处理单」的唯一性标识,记为 RequestId 。 消费者如果作为调用链中的一个中间环节,那么还需要附加携带 TransactionId 作为全局流水号,为一次完整的全链路跟踪提供线索。

提供者,每次在处理请求时,需要记录上游提供的 RequestId ,同时还需要对响应结果,生成一个 ResponseId ,并携带 RequestId 作为关联标识。

提交-补偿模式的REST-like实现示例

消费方请求示例

  • C-C Commit Request cURL Syntax
curl -X PUT -H "Content-Type: application/json" -H "User-Agent: <ConsumerId>"  \\
 -H "Referer: <TransactionId>" -d <RequestBody> "https://<domain>/<path>/<RequestId>[?v=<Version>]"
  • C-C Commit Request cURL Example
curl -X PUT -H "Content-Type: application/json" -H "User-Agent: REST-like-Consumer" \\
 -H "Referer: 0987654" -d '{ "title":"This is an request example." }' "https://api.yanjiong.wang/article/new/67890"
  • C-C Commit Request HTTP/1.1 Example
PUT /article/new/67890 HTTP/1.1
Host: yanjiong.wang
Content-Type: application/json
User-Agent: REST-like-Consumer
Referer: 0987654

{
    "title":"This is an request example."
}

提供方请求示例

  • C-C Commit Response HTTP/1.1 Syntax
HTTP/1.1 <Status Code>
Server: <Provider>/<Version>
Content-type: application/json
[Date: <Date>]
[Last-Modified: <Date>]
[ETag: <Content Hash>]
[X-CC-RequestId: <RequestId>]
  • C-C Commit Response HTTP/1.1 Example
HTTP/1.1 200 OK
Server: REST-like-Provider/2.1
Date: Wed, 21 Dec 2016 15:03:33 GMT
Last-Modified: Tue, 20 Dec 2016 08:37:25 GMT
ETag: W/"ebf-1591b60c988"
X-CC-RequestId: 67890

{
    "title":"This is an response example."
}