首页 文章

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

提问于
浏览
10

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

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

通常如此

...
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之前注册服务静态MW来确保静态内容不存储会话 . 但这只在某种程度上有所帮助;如果实际上存在应该并行调用的API方法,则需要一些真正的并发方法来进行会话更新(IMO,当涉及到并发问题时,每一个都努力将问题发生的可能性降至最低而不是解决实际问题 . 问题肯定会出错) .

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

  • Prevent parallel requests within the same session completely by modifying my client to make sure it calls all API methods serially.

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

  • Make sure each session write is preceded by a session reload and make the entire reload-modify-write operation atomic.

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

  • request A重新加载会话

  • 请求A修改session.A = 'foo'

  • 请求B重新加载会话(并且不会看到session.A)

  • request A存储会话

  • 请求B修改session.B = 'bar'

  • 请求B存储会话,覆盖以前的存储,以便缺少session.A.

所以本质上我需要让每个reload-modify-store原子,这基本上意味着阻塞线程?感觉不对,我不知道该怎么做 .

  • Stop using the session in this way altogether and pass necessary state as params to each request or by some other means.

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

  • ???

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

1 回答

  • 1

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

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

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

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

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

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

相关问题