使 POST 请求幂等

Max*_*Max 8 rest post distributed http idempotent

我一直在寻找一种方法来设计我的 API,使其具有幂等性,这意味着其中一些方法是为了使我的 POST 请求路由具有幂等性,我偶然发现了这篇文章。

(如果我理解的不对,请纠正我!)

其中对总体思路有很好的解释。但缺少的是他自己实施的一些例子。

有人问文章作者,他如何保证原子性?所以作者添加了一个代码示例。

本质上,在他的代码示例中有两种情况,

如果一切顺利的话流程:

  • 在数据库上打开一个事务,保存需要通过 POST 请求更改的数据
  • 在此事务中,执行所需的更改
  • 在 Redis 存储内设置Idempotency-key键和值,这是对客户端的响应
  • 设置该键的过期时间
  • 提交交易

如果代码内部出现问题,流程如下:

  • 并且函数流程内发生异常。
  • 执行事务回滚

注意,打开的事务是针对某个DB的,我们称他为A。但是,它与他也使用的redis存储无关,这意味着事务的回滚只会影响DB A。

因此,它涵盖了代码内部发生某些事情导致无法完成交易的情况。

但是,如果运行代码的机器在已经执行完Set expire time to that key并且即将运行事务提交的状态下崩溃,会发生什么情况?

在这种情况下,密钥将在 Redis 存储中可用,但事务尚未提交。这将导致这样的情况:服务确定所需的更改已经发生,但事实并非如此,机器在完成之前就发生了故障。

我需要以这样的方式设计API:如果redis中的数据更改或键和值的设置失败,它们都会回滚。

这个问题的解决办法是什么?

如何保证在一个数据库中更改所需数据的原子性,同时在redis中设置密钥和所需响应,如果其中任何一个失败,则将它们都回滚?(包括机器在操作过程中崩溃的情况)

请在回答时添加代码示例!我使用与本文中相同的技术(nodejs、redis、mongo - 用于数据本身)

谢谢 :)

baz*_*lia 5

根据您在问题中共享的代码示例,您想要的行为是确保在将幂等键设置到 Redis 中表示此事务已经发生的时刻和事务发生的时刻之间,服务器上没有崩溃,事实上,持久化在你的数据库中。

\n\n

但是,当将 Redis 和另一个数据库一起使用时,您会遇到两个独立的故障点,并且两个操作在不同时刻按顺序执行(即使它们同时异步执行,也不能保证服务器赢\xe2\x80\ x99t 在其中任何一个完成之前崩溃)。

\n\n

相反,您可以做的是在事务中包含一条插入语句,该语句指向包含此请求的相关信息(包括幂等键)的表。由于 ACID 属性确保原子性,它保证事务上的所有语句都成功执行,或者都不执行,这意味着如果事务成功,您的幂等性密钥将在数据库中可用。

\n\n

您仍然可以使用 Redis,因为它\xe2\x80\x99s 会提供比数据库更快的结果。

\n\n

下面提供了一个代码示例,但最好考虑一下插入 Redis 和数据库之间的失败与您的业务之间的相关性(是否可以用其他策略来处理?)以避免过度设计。

\n\n
async function execute(idempotentKey) {\n  try {\n    // append to the query statement an insert into executions table.\n    // this will be persisted with the transaction\n    query = ```\n        UPDATE firsttable SET ...;\n        UPDATE secondtable SET ...;\n        INSERT INTO executions (idempotent_key, success) VALUES (:idempotent_key, true);\n    ```;\n\n    const db = await dbConnection();\n    await db.beginTransaction();\n    await db.execute(query);\n\n    // we\'re setting a key on redis with a value: "false".\n    await redisClient.setAsync(idempotentKey, false, \'EX\', process.env.KEY_EXPIRE_TIME);\n\n    /*\n      if server crashes exactly here, idempotent key will be on redis with false as value.\n      in this case, there are two possibilities: commit to database suceeded or not.\n      if on next request redis provides a false value, query database to verify if transaction was executed.\n    */\n\n    await db.commit();\n\n    // you can now set key value to true, meaning commit suceeded and you won\'t need to query database to verify that.\n    await redis.setAsync(idempotentKey, true);\n  } catch (err) {\n    await db.rollback();\n    throw err;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n