Node / Express:使用会话存储状态时的并发问题

时间:2015-02-19 09:03:56

标签: node.js session express concurrency

所以,我对此进行了相当多的搜索,并发现了几个有些类似的问题,但没有一个真正解决这个问题,所以我认为这应该是一个自己的问题。

我有一个明确的应用程序,有一堆路由修改会话以保持状态。事情是,如果有多个并行请求,会话将不时因请求之间的竞争条件而被覆盖。

通常

...
app.use(express.static('/public'));
app.use(session(...));
app.route('methodA').get(function(req, res, next) {
    doSomethingSlow().then(function() {
        req.session.a = 'foo';
        res.send(...);
    }
});
app.route('methodB').get(function(req, res, next) {
    doSomethingElseSlow().then(function() {
        req.session.b = 'bar';
        res.send(...);
    }
});

基本上问题很简单,例如在this回答中描述。 Express将会话存储在res.end()中,但在处理methodA请求时,methodB请求可能同时修改了会话,以便在methodA存储会话时,它将覆盖methodB所做的任何更改。因此,即使节点是单线程的,并且所有请求都由同一个线程提供服务,一旦任何方法执行异步操作,我们就会最终出现并发问题,从而允许同时处理其他请求。

然而,我正在努力决定如何解决这个问题。我找到的所有答案仅列出最小化发生这种情况的概率的方式,例如:通过在会话MW之前注册serve-static MW来确保静态内容不存储会话。但这只在某种程度上有所帮助;如果实际上存在应该并行调用的API方法,则需要一些真正的并发方法来进行会话更新(IMO,当涉及并发问题时,每个“解决方案”都力求最小化出现问题的可能性而不是解决问题实际问题肯定会出问题。)

基本上这些是我目前正在探索的替代方案:

  1. 通过修改我的客户端以确保它以串行方式调用所有API方法,完全阻止同一会话中的并行请求。

    这可能是可能的,但会对我的架构产生相当大的影响,并可能影响性能。它也避免了问题,而不是解决它,如果我在我的客户端出错或者API被另一个客户端使用,我可能仍会随机遇到这个问题,所以它感觉不太健壮。

  2. 确保每次会话写入之前都有会话重新加载,并使整个重新加载 - 修改 - 写入操作成为原子。

    我不知道如何实现这一目标。即使我修改res.end()以在修改和存储之前重新加载会话 ,因为读取和写入会话是异步I / O似乎可能会发生这种情况:

    • 请求A重新加载会话
    • 请求A修改session.A ='foo'
    • 请求B重新加载会话(并且不会看到session.A)
    • 请求A存储会话
    • 请求B修改session.B ='bar'
    • 请求B存储会话,覆盖以前的商店,以便缺少session.A
  3. 所以本质上我需要让每个reload-modify-store原子,这基本上意味着阻塞线程?感觉不对,我不知道怎么做。

    1. 完全停止以这种方式使用会话,并将必要的状态作为参数传递给每个请求或通过其他方式。

      这也避免了问题而不是解决问题。它也会对我的项目产生巨大影响。但可以肯定的是,这可能是最好的方式。

    2. ???

    3. 任何人都有任何想法如何以健壮的方式解决这个问题? 谢谢!

1 个答案:

答案 0 :(得分:1)

可能的解决方案是为express创建一个简单的中间件,它将维护所有当前请求的列表,并对来自同一会话的任何请求进行排队。在首先处理先前对该session-id的请求之前,它不会调用next()。

每次请求进来时,它都可以将其存储在一个对象中,其中key是session-id cookie名称,值是当前session-id请求的数组。

{ 'session-id1': [... queued requests ...], 'session-id2': ... }

当请求完成后,它将从数组中删除它的self并触发要处理的数组中的下一个请求。

您还可以在标头中为每个请求添加一个标志,以允许并行运行不需要排队的请求(例如,它们不会写入会话),从而在不需要排队时提高性能

您应该能够实现这一点,而无需更改您的应用程序。您还可以将标头中的选择退出选项更改为选择加入选项,这意味着它将像通常那样同时处理所有请求,除非另有说明。

相关问题