RESTful API 中使用的 Etags 仍然容易受到竞争条件的影响

Met*_*orm 6 python database rest concurrency etag

也许我在这里忽略了一些简单而明显的东西,但这里是:

因此,HTTP 请求/响应中 Etag 标头的功能之一是强制执行并发性,即多个客户端无法覆盖彼此对资源的编辑(通常在执行 PUT 请求时)。我认为这部分是众所周知的。

我不太确定的是后端/API 实现如何在没有竞争条件的情况下实际实现这一点;例如:

设置:

  • RESTful API 位于标准关系数据库之上,使用 ORM 进行所有交互(例如 SQL Alchemy 或 Postgres)。
  • Etag 基于资源的“上次更新时间”
  • Web 框架(Flask)位于多线程/进程网络服务器(nginx + gunicorn)之后,因此可以同时处理多个请求。

问题:

  • 客户端 1 和客户端 2 都请求资源(获取请求),现在都具有相同的 Etag。
  • Client 1 和 Client 2 同时发送 PUT 请求来更新资源。API 接收请求,继续使用 ORM 从数据库中获取所需的信息,然后将请求 Etag 与数据库中的“上次更新时间”进行比较……它们匹配,因此每个都是有效的请求。每个请求继续并将更新提交到数据库。
  • 每次提交都是同步/阻塞事务,因此一个请求将在另一个请求之前进入,因此一个请求将覆盖其他更改。
  • 这不会破坏 Etag 的目的吗?

我能想到的唯一万无一失的解决方案是也让数据库执行检查,例如在更新查询中。我错过了什么吗?

PS 由于使用的框架被标记为 Python,但这应该是一个语言/框架不可知的问题。

Eva*_*van 1

Etag可以通过多种方式实现,而不仅仅是last updated time。如果您选择Etag纯粹基于 来实现last updated time,那么为什么不直接使用Last-Modifiedheader 呢?

如果您要将更多信息编码到Etag有关底层资源的信息中,您就不会受到上面概述的竞争条件的影响。

我能想到的唯一万无一失的解决方案是让数据库也执行检查,例如在更新查询中。我错过了什么吗?

这就是你的答案。


另一种选择是向每个资源添加一个版本,该版本会在每次成功更新时递增。更新资源时,请在WHERE. 另外,设置version = version + 1. 如果自上次请求以来资源已更新,则更新将失败,因为找不到记录。这消除了锁定的需要。