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