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

Node.js Node/Express:使用会话存储状态时的并发问题,node.js,session,express,concurrency,Node.js,Session,Express,Concurrency,所以,我搜索了很多,发现了几个类似的问题,没有一个真正解决了这个问题,所以我认为这应该是一个自己的问题 我有一个express应用程序,它有一系列路由,可以修改会话以保持状态。问题是,如果有多个并行请求,由于请求之间的竞争条件,会话将不时被覆盖 典型地 ... app.use(express.static('/public')); app.use(session(...)); app.route('methodA').get(function(req, res, next) { doSo

所以,我搜索了很多,发现了几个类似的问题,没有一个真正解决了这个问题,所以我认为这应该是一个自己的问题

我有一个express应用程序,它有一系列路由,可以修改会话以保持状态。问题是,如果有多个并行请求,由于请求之间的竞争条件,会话将不时被覆盖

典型地

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

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

基本上,这些是我目前正在探索的备选方案:

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

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

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

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

    • 请求A将重新加载会话
    • 请求A修改会话。A='foo'
    • 请求B重新加载会话(不会看到会话A)
    • 请求A存储会话
    • 请求B修改会话。B='bar'
    • 请求B存储会话,覆盖以前的存储,因此会话.A丢失
  • 所以本质上我需要使每个重载修改存储原子化,这基本上意味着阻塞线程?感觉不对,我不知道怎么做

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

    这也避免了问题,而不是解决问题。这也会对我的项目产生巨大的影响。当然,这可能是最好的办法

  • 有人知道如何以一种强有力的方式解决这个问题吗?
    谢谢

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

    每次请求传入时,它都可以将其存储在对象中,其中键是会话id cookie名称,值是会话id的当前请求数组

    
    {
    “会话id1”:[…排队请求…],
    “会话id2”:。。。
    }
    

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

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


    您应该能够实现这一点,而无需更改应用程序。您还可以将标题中的opt-out选项更改为opt-in选项,这意味着它将像通常一样并发处理所有请求,除非另有规定。

    我们最近遇到了相同的问题。似乎这就是野兽的本性,除了编写我们自己的会话管理器或切换到Redis之外的其他东西,我们没有想出任何更好的选择。我们希望从一开始就做了3件事,但现在改做太贵了。我们的最终解决方案是减少使用会话的请求数(我们使用的是Sails,它会对每个请求执行get/set会话)。我们有一个对象,所有请求在进入节点之前都经过过滤,因此我们将其修改为请求队列(您的选项#1),效果很好。从技术上讲,使用事务更新()使用Redis可以做到这一点,但您必须自己实现,因为connect Redis不支持它。它还可能需要在express会话中进行更改。乐观锁定(在Redis中实现)的问题是,如果更新因并发更新而失败,则必须重新加载新状态,重播更改并再次写入。您可以使用req.session.save()立即保存会话,而不是等待res.end完成it@Omiron那会有什么帮助?仍然没有原子性阻止两个客户端同时执行load modify save