Rest 使POST请求幂等

Rest 使POST请求幂等,rest,http,post,distributed,idempotent,Rest,Http,Post,Distributed,Idempotent,我一直在寻找一种方法来设计我的API,使其成为幂等的,这意味着其中的一些方法将使我的POST请求路由成为幂等的,我偶然发现了这篇文章 (如果我理解了一些不正确的事情,请纠正我!) 在这本书中,有一个很好的总体思路的解释。但缺少的是一些他自己实施的例子 有人问文章的作者,他如何保证原子性?因此,作者添加了一个代码示例 基本上有两种情况, 如果一切顺利,流程如下: 在数据库上打开一个事务,该事务保存POST请求需要更改的数据 在此事务中,执行所需的更改 在Redis存储中设置幂等键键和值,这是对客

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

(如果我理解了一些不正确的事情,请纠正我!)

在这本书中,有一个很好的总体思路的解释。但缺少的是一些他自己实施的例子

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

基本上有两种情况,

如果一切顺利,流程如下:

  • 在数据库上打开一个事务,该事务保存POST请求需要更改的数据
  • 在此事务中,执行所需的更改
  • 在Redis存储中设置
    幂等键
    键和值,这是对客户端的响应
  • 将过期时间设置为该密钥
  • 提交事务
如果代码中的某些内容出错,则流程为:

  • 函数流中出现异常
  • 执行对事务的回滚
注意,打开的事务是针对某个DB的,让我们称他为a。 但是,这与他也使用的redis存储无关,这意味着事务回滚只会影响DB A

因此,它涵盖了当代码中发生了使事务无法完成的事情时的情况

但是,如果运行代码的机器在其已执行
设置该键的过期时间
且即将运行事务提交的状态下崩溃,会发生什么

在这种情况下,密钥将在redis存储中可用,但事务尚未提交。 这将导致这样一种情况,即服务确信所需的更改已经发生,但它们没有发生,机器在完成之前就失败了

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

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

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


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


谢谢:)

根据您在问题中分享的代码示例,您希望的行为是确保在Redis中设置幂等键表示此事务已发生的时刻和该事务实际保留在数据库中的时刻之间,服务器上没有崩溃

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

相反,您可以在事务中包含一条insert语句,该语句指向一个表,该表包含关于该请求的相关信息,包括幂等键。由于ACID属性确保原子性,因此它可以保证事务上的所有语句都能成功执行,也可以不执行任何语句,这意味着如果事务成功,您的幂等键将在数据库中可用

您仍然可以使用Redis,因为它将提供比数据库更快的结果

下面提供了一个代码示例,但考虑一下insert to Redis和数据库之间的故障与您的业务有多大关系(是否可以用另一种策略来处理?),以避免过度工程化,这可能是一件好事

async function execute(idempotentKey) {
  try {
    // append to the query statement an insert into executions table.
    // this will be persisted with the transaction
    query = ```
        UPDATE firsttable SET ...;
        UPDATE secondtable SET ...;
        INSERT INTO executions (idempotent_key, success) VALUES (:idempotent_key, true);
    ```;

    const db = await dbConnection();
    await db.beginTransaction();
    await db.execute(query);

    // we're setting a key on redis with a value: "false".
    await redisClient.setAsync(idempotentKey, false, 'EX', process.env.KEY_EXPIRE_TIME);

    /*
      if server crashes exactly here, idempotent key will be on redis with false as value.
      in this case, there are two possibilities: commit to database suceeded or not.
      if on next request redis provides a false value, query database to verify if transaction was executed.
    */

    await db.commit();

    // you can now set key value to true, meaning commit suceeded and you won't need to query database to verify that.
    await redis.setAsync(idempotentKey, true);
  } catch (err) {
    await db.rollback();
    throw err;
  }
}

你在使用什么技术?Node.JS。网?如果我们不知道您使用的是什么语言/技术,我们将无法提供代码:)我使用的技术与本文中相同,我将在问题中指出。谢谢好的,这就是使用相同的数据库来确保原子性,这可能是实现这一点的唯一方法,但如果是这样,那么为什么本文作者(我在开始时提到)会使用两个完全不同的数据库呢?我们是不是遗漏了什么?或者他只是在误导?无论如何,我不确定这是否是最好的方法,它看起来像是将关于幂等键的数据和请求的结果保存在与数据本身相同的数据库中,这有点奇怪。。。此外,如果数据本身的DB不适合幂等键-值对,那么如果我们使用两个具有重复数据的DB,那么检查之前是否发出过请求的逻辑会非常复杂。数据库是边缘情况的后备方案。几乎所有的时间,如果不是每次,你只能访问Redis。正如我在回答中提到的,考虑到这种边缘情况可能过于工程化,我想抽象一下,如果数据保存在Redis中,提交到数据库也会成功。这也是这篇文章的作者正在考虑的。Redis是为包括缓存在内的许多用例而设计的,所有相关的实现都是过期的,而您的数据库不会提供。只有在非常重要的情况下,如果Redis上的幂等键为false(edge case),您才可以检查数据库中的幂等表。而且,数据复制(即使是暂时的,因为Redis是缓存,最终其中的数据将被鞭打)会不会被视为一个问题?这是大规模应用的解决方案吗?将相同的数据写入两个数据库?复制本身不是问题。你这样做是因为你仍然希望从Redis中获益,因为它是为你所需要的东西而设计的