如何避免两个并发的API请求破坏文档验证背后的逻辑?

时间:2019-04-11 09:35:06

标签: node.js mongodb mongoose

我有一个API,为了插入新商品,需要对其进行验证。验证基本上是类型验证器(stringnumberDate等),并查询数据库以检查“用户”是否在同一日期具有“项目”,如果成功,则验证失败。

伪代码是这样的:

const Item = require("./models/item");
function post(newDoc){

  let errors = await checkForDocErrors(newDoc)
  if (errors) {
    throw errors;
  }

  let itemCreated = await Item.create(newDoc);

  return itemCreated;


}

我的问题是,如果我同时执行两个并发请求:

const request = require("superagent");

// Inserts a new Item
request.post('http://127.0.0.1:5000/api/item')
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Water Bottle"
})
/* 
   Inserts a new Item, which shouldn't do. Resulting in two items having the
   same date.
*/
request.post('http://127.0.0.1:5000/api/item')
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Toothpick"
})


两者都将成功,这不应该成功,因为“用户”在同一日期不能有两个“项目”。

如果我在第一个完成后执行了第二个,一切都会按预期进行。

request.post('http://127.0.0.1:5000/api/item') // Inserts a new Item
.send({
  "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
  "start_date": "2019-04-02",
  "name": "Water Bottle"
})
.then((res) => {
  // It is not successful since there is already an item with that date
  // as expected
  request.post('http://127.0.0.1:5000/api/item') 
  .send({
    "id_user": "6c67ea36-5bfd-48ec-af62-cede984dff9d",
    "start_date": "2019-04-02",
    "name": "Toothpick"
  })
})

为避免这种情况,我向一个请求发送了一系列文档,但我想防止出现此问题,或者至少避免发生这种情况。

解决方案

我创建了一个Redis服务器。使用了redis-lock包,并包裹了POST路线。

var client = require("redis").createClient()
var lock = require("redis-lock")(client);
var itemController = require('./controllers/item');
router.post('/', function(req, res){
  let userId = "";
  if (typeof req.body === 'object' && typeof req.body.id_user === 'string') {
    userId = req.body.id_user;
  }
  lock('POST ' + req.path + userId, async function(done){
    try {
      let result = await itemController.post(req.body)
      res.json(result);
    } catch (e) {
      res.status(500).send("Server Error");
    }
    done()

  })
}

谢谢。

2 个答案:

答案 0 :(得分:2)

解释

那是race condition

  

两个或多个线程可以访问共享数据,并且它们试图同时更改它们

What is a race condition?

解决方案:

在这种情况下,有很多方法可以防止冲突数据,锁定是1选项。
您可以锁定应用程序级别或数据库级别...但是我希望您在选择其中任何一个之前先阅读此线程。

Optimistic vs. Pessimistic locking
快速解决方案:pessimistic-lock https://www.npmjs.com/package/redis-lock

答案 1 :(得分:1)

您应该创建包含BottomNavigationViewid_user字段的复合索引复合主键。这样可以确保无法为同一用户创建具有相同日期的文档,并且如果您尝试这样做,数据库将引发错误。 Composite index with mongoose

您还可以使用交易。为此,您应该在事务内执行start_datefind方法,以确保不会在同一文档上执行任何并发查询。 Mongoose transactions tutorial

More infos

我将使用唯一的复合索引,在您的特定情况下,该索引应类似于

create