koa-session 与高并发

koa-session 是一个常见的session 存储中间件,除了支持在内存中存储session, 也支持在数据库中存储session。

内存存储

首先我们说下内存存储,如果是存储在内存中,则天然能处理高并发场景。下一个请求读取的,一定是上一个修改完成的数据。

但在nodejs 生产环境中,却用处不大。因为生产环境中一般会启动多个进程,进程之间的内存是不共享的。要实现进程间通讯,会比较麻烦,并且高并发场景下也可能出现竞争读取了。

数据库存储

一般会把session 写入到Redis或者mongo 中。

但这在高并发场景下,容易出现读取了过期数据的现象。例如两个请求几乎同时去读取用户信息,但前一个修改了用户昵称,在写入数据库之前,后一个请求读取了,则读到了过期数据。

这种场景还好,但如果是涉及支付的场景则不可接受。

并且,在koa-session 的实现中,如果不了解源码,天然会掉入高并发脏数据漏洞中。

在koa-session 可以同步的读写session 数据,例如

const name=this.ctx.session.name    // get key
this.ctx.session.name=newName       // set key 
...                                 // other code , 耗时操作

代码看上去是同步执行的,但如果这时候有高并发请求去读取name 字段,读取到的是老的name 。这是为什么呢?看下源码的实现。github

return async function session(ctx, next) {
    const sess = ctx[CONTEXT_SESSION];
    if (sess.store) await sess.initFromExternal();    // http 请求到来时读取session
    try {
      await next();                                   // 等待整个请求的耗时结束
    } catch (err) {
      throw err;
    } finally {
      if (opts.autoCommit) {
        await sess.commit();                          // 将session 写回到数据库
      }
    }
  };

假如修改session 后请求执行了1s,则这1s 内server 内存session 更新了,但并没有提交到数据库。

修复方法比较简单,对于重要的操作,更新后立即手动提交到数据库,save 函数

const name=this.ctx.session.name    // get key
this.ctx.session.name=newName       // set key 
this.ctx.session.save()             // 立即保存到数据库
...                                 // other code , 耗时操作

高并发

在对数据一致性有强依赖的情况下,还需要合并读写操作为原子操作。例如上面的读取oldName, 设置newName , 应当合并为一个数据库更新语句。

oldDoc=db.update({_id},{$set:{name:newName}})
oldNmae=oldDoc.name

Leave a Comment

邮箱地址不会被公开。 必填项已用*标注