基于Http的ETags和If-Modified-Since实现乐观并发性


HTTP的特点是ETags和条件性请求,并启用乐观的并发性。

ETag
ETag(又称实体标记entity-tag)解决了 "丢失更新 "的问题,即一个API的两个客户端已经收到了一个实体的版本的数据。但是,另一个客户端在另一个客户端之前更新了该实体:第二个更新导致第一个更新 "丢失"。

ETag通过用实体标签(表示法的哈希值、版本等)对资源进行版本管理来解决意外的覆盖。当客户端请求一个资源时,服务器可以在响应中包括一个带有实体标签值的ETag验证器标头域。

资源的URI与该实体标记一起构成了一个实体的特定版本的标识符。

当客户端请求对实体进行修改时,它包括实体标签作为基础版本,并带有一个条件头域(如If-Match)。服务器响应412(前提条件失败),客户端可以检索最新版本,重新应用他们的修改,并重新发送。

即使你使用日期和时间,HTTP也为你提供了其他涉及修改日期的前提条件头域。

If-Unmodified-Since和If-Modified-Since
If-Unmodified-Since和If-Modified-Since前提条件标头域允许你传递修改日期的前提条件,使请求成为有条件的。当前提条件没有满足时,412(前提条件失败)状态代码将出现在响应中,或者对于GET或HEAD,304(未修改)状态代码将出现在响应中。

在有条件的请求中,支持修改日期的资源的初始GET将包括一个Last-Modified头域验证器。

Last-Modified验证器的形式是HTTP-date.。

RFC 7232,HTTP 1.1规范,第2.3节描述了实体标签:

实体标签是一个不透明的验证器,用于区分同一资源的多种表现形式,无论这些多种表现形式是由于资源状态随时间的变化、内容协商导致多种表现形式同时有效,还是两者都是如此。

这意味着ETag值取决于内容类型,所以同一资源的两个不同的输出数据应该有不同的ETag值(例如,一个使用gzip编码,一个则没有使用压缩。)

这也意味着ETag值对请求者来说是不透明的,但确实指出了ETag的意图之一,即由于缺乏准确性而成为使用日期-时间戳的替代品。

PATCH
将PATCH与JSONPatch这样的东西一起使用,似乎可以通过提供更细化的变化内容来帮助缓解冲突。

从技术上讲是的,要实现这一点并不容易。

ETag指定了整个资源的那个版本的标签,而不是任何一个字段。

虽然比较一个资源的两个版本之间的变化(记住这些版本可能不相邻)可能是处理这个问题的一种技术,但在同一资源的任意版本之间创建delta 是不容易的。你可以引入这类东西:类似事件溯源的东西可能会实现这一点

但请记住,资源的属性之间可能存在相互依赖的关系,仅仅因为当前的请求改变了一个自资源被检索以来没有改变的属性,并不意味着仍然存在冲突。

Last-Modified
请记住,Last-Modified使用HTTP-date格式,所以Last-Modified只支持秒级的粒度。如果有多个来源服务器,可能需要超过第二个粒度的数据才能100%准确。

If-Unmodified-Since
If-Unmodified-Since与PUT、POST、DELETE和PATCH等状态改变方法一起使用,以避免意外的覆盖(丢失更新)。If-Unmodified-Since的前提条件是,只有当这个实体在提供的日期时间后没有改变时,才会更新它。使用If-Unmodified-Since来避免丢失更新的问题,当第二粒度不是问题时。

If-Modified-Since
当与GET或HEAD一起使用时,If-Modified-Since头域施加了一个前提条件,即如果识别的资源的修改日期不比提供的日期更近,则以304(未修改)进行响应,而不是以实体表示。使用If-Modified-Since来避免重新传输相同的数据。

409 (冲突)
409 (Conflict)听起来像是对有条件的PUT/POST/PATCH请求的适当响应,但412 (Precondition Failed)是预期的。当资源的当前状态意味着服务器不能改变它时,应该使用响应状态代码409。另外,如果你选择不使用HTTP的先决条件功能,并且在实体的表示中包含了一些用于版本管理的东西(比如最后修改日期,见上文),那么409(冲突)是合适的,以表示潜在的意外覆盖或丢失更新。