【问题标题】:parallel requests to node.js, connect-mongo, session overwritten对 node.js、connect-mongo、会话覆盖的并行请求
【发布时间】:2012-01-20 09:25:26
【问题描述】:

在当前的项目(一种商店系统)中,我使用带有 expressJSconnect-mongo 的 node.js 作为会话存储。在客户端,我在启动时使用一个请求来创建一个新会话,然后将多个并行请求发送到 node.js 服务器。 因为这些并行请求会更改会话,所以这些更改似乎会相互覆盖,尽管它们当然会更改会话的不同对象。

示例(所有 3 个请求同时启动):

  • 请求 A 将一些产品推送到数组 req.session.productHist['abc']
  • 请求 B 将产品推送到 req.session.productHist['def']
  • 请求 C 需要一些时间,但不会更改会话

因为请求 C 在请求 A 和 B 之后完成,但在它们完成之前开始,所以它似乎用请求 C 开始时保持的值(null)覆盖了 session.productHist

我该如何解决这个问题?

更新:

一些带有控制台输出的示例代码:

var url = require('url'),
    express = require('express'),
    MongoStore = require('connect-mongo');

var aDay = 24*60*60*1000;

var app = express.createServer();

app.configure(function(){
  app.use(express.cookieParser());
  app.use(express.session({
    secret: "secret",
    store: new MongoStore({ db: 'lmsCache' }),
    maxAge: aDay
    })
  );
  app.use(express.methodOverride());    app.use(express.bodyParser());
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(app.router);
  app.use(express.logger());
});


function sendStringified(req, res, data) {
    data = JSON.stringify(data);
    if (req.url_query.callback) { data = req.url_query.callback + "(" + data + ");"; }
    res.send(data);
}

function parseParams(req,res,next) {
  req.url_query = url.parse(req.url,true).query;
  next();
}

function doExpensiveStuff(req,res,next) {
  console.log("######################### init start");
  [...]
}


app.get('/init', parseParams, doExpensiveStuff, function(req,res) {
  console.log("init: session.productHist: " + JSON.stringify(req.session.productHist));
  console.log("######################### init end");
  sendStringified(req,res,null);
});


app.get('/products', parseParams, function(req,res) {

  console.log("######################### products "+req.url_query.category+" start");

  if(!req.session.productHist[req.url_query.category])
    req.session.productHist[req.url_query.category] = [];

  for(var i=0;i<2;i++) {
      req.session.productHist[req.url_query.category].push({ "id": new Date().toGMTString() }); 
  } 

  console.log("products: session.productHist: " + JSON.stringify(req.session.productHist));
  console.log("######################### products "+req.url_query.category+" end");
  sendStringified(req,res,[]);
});

app.get('/newSession', parseParams, function(req,res) {
  console.log("######################### newSession");
  req.session.productHist = {};
  sendStringified(req,res,true);
});  

app.listen(8080);  

time = new Date().toGMTString();  

console.log('Server starting at: ' + time);  

控制台日志:

服务器开始时间:2011 年 12 月 15 日星期四 15:50:37 GMT

################### newSession
################### 初始化启动
###################产品-1开始

产品:session.productHist: {"-1":[{"id":"Thu, 2011 年 12 月 15 日 15:50:40 GMT"},{"id":"Thu, 2011 年 12 月 15 日 15:50 :40 GMT"}]}

###################产品-1结束


初始化:session.productHist:{}

################### 初始化结束


[...]

################## 产品 -1 开始

产品:session.productHist: {"-1":[{"id":"周四, 2011 年 12 月 15 日 15:50:53 GMT"},{"id":"周四,2011 年 12 月 15 日 15:50:53 GMT"}]}

###################产品-1结束

【问题讨论】:

  • A&B之后不能请求C吗?
  • 我希望能够以某种方式发送并行请求。我可以在 C 完成后发送 A&B,但这会降低用户体验
  • 请求C到底做了什么..?
  • 请求 C 返回初始化数据,例如用户数据、要显示的类别等。请求 A 和 B 返回具有不同参数(来自不同类别)的产品,并应将它们添加到产品历史记录中,以便它们不会不会在以后的请求中再次显示
  • 你肯定做错了什么。例如,如果您推送到数组或在 req.session 上定义不同的属性,则不应被覆盖。

标签: session mongodb node.js parallel-processing express


【解决方案1】:

我想我已经找到了这个棘手问题的答案。

来自Express.js 文档:

Properties on req.session are automatically saved on a response

短版

当您设置会话变量 (req.session.my_var = value) 时,它实际上并没有被保存(当时),而是稍后(当您发送响应时,在您的情况下是当您执行 res.send 时)。这导致了你的问题。

加长版

那么这到底是什么意思?

  1. 您发出请求,然后获取会话变量(处于状态 A),然后对其进行处理(这需要一些时间)
  2. 您发出另一个请求并获取会话变量(它仍处于状态 A,因为尚未发送响应)并对其进行一些更改
  3. 现在会话处理等已经完成,因此您从 1) 发送响应,从而修改会话变量并将其带入状态 B
  4. 在您发送来自 2) 的响应之后,“有趣”的部分就来了。现在您实际上并没有修改当前会话(已更新为状态 B),因为响应被延迟了,所以您实际更改的是状态 A 的会话,将其带入状态 C => 这意味着所有状态 B 的修改丢失了!

【讨论】:

  • 我认为这是问题所在,但您有解决方案吗?我想我可以在 node.js 内存中镜像整个会话,但这并不是我认为的一个很好的解决方案,因为它会使 mongo 存储在一定程度上变得不必要(并产生很大的开销)。我想知道是否有一些现成的解决方案可以解决这个问题,因为我当然不是第一个遇到这种情况的人。
  • 不一定是完美的解决方案,但最好自己处理,然后调用req.sessionStore.reload(callback_function)并在回调函数中更改会话并立即调用res.send。最好的解决方案是拥有一个通信通道(UNIX 套接字、Redis)并在您更改会话时发送通知(从而模拟锁,例如 msg: 'session:id locked')以及发送响应时触发另一条消息('session:id unlocked')。如果锁定(在“解锁”重新加载会话上),每个请求都应该监听而不修改会话。
  • 感谢您的帮助 :) 我会尝试一下,直到找到更好的解决方案(如果我找到了)。
猜你喜欢
  • 1970-01-01
  • 2013-02-23
  • 1970-01-01
  • 2021-05-28
  • 1970-01-01
  • 1970-01-01
  • 2013-06-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多