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

标签 python database rest concurrency etag

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

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

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

设置:

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

问题:

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

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

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

最佳答案

这实际上是一个关于如何使用 ORM 进行更新的问题,而不是关于 ETag 的问题。

假设有 2 个进程同时将钱转入一个银行账户——它们都读取旧余额,添加一些,然后写入新余额。其中一个传输丢失了。

当您使用关系数据库编写时,这些问题的解决方案是将读取和写入放在同一个事务中,然后使用 SELECT FOR UPDATE 读取数据和/或确保您具有适当的隔离级别设置。

各种 ORM 实现都支持事务,因此在同一个事务中读取、检查和写入将很容易。如果您设置 SERIALIZABLE 隔离级别,那么这将足以解决竞争条件,但您可能必须处理死锁。

ORM 通常也以某种方式支持 SELECT FOR UPDATE。这将允许您使用默认的 READ COMMITTED 隔离级别编写安全代码。如果你用谷歌搜索 SELECT FOR UPDATE 和你的 ORM,它可能会告诉你如何去做。

两种情况下(可序列化隔离级别或选择更新),数据库将通过在您读取实体行时锁定该实体行来解决问题。如果另一个请求进来并试图在您的事务提交之前读取该实体,它将被迫等待。

关于python - RESTful API 中使用的 Etag 仍然容易受到竞争条件的影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34428046/

相关文章:

python - 如何从 matplotlib 中删除灰色边框

mysql - sqoop导入mysql时自动创建hive表注释?

java - org.glassfish.jersey.internal.RuntimeDelegateImpl 未找到

python - 使用检查返回包内定义的类列表

python - TypeError : a bytes-like object is required, 不是 'Binary'

php - 如何在 Laravel 中划分读/写数据库设置?

php - 用 laravel 和 eloquent 处理 TimeZone

java - 无法在 'setRequestHeader' 上执行 'XMLHttpRequest' [...] 不是有效的 HTTP header 字段值

jquery - ajax 中的 DELETE 请求给出 301 移动响应

python - 如何创建动态过滤器?