也许我在这里忽略了一些简单而明显的东西,但是这里有:
因此,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/