Node.js express-session + redis单实例问题
node.js express-session + redis single instance issue
我使用express-session模块来处理我的node.js用户会话。默认情况下,它允许每个用户进行多个会话。我需要限制每个用户一个会话。我来到以下解决方案:存储user_id:session_id对在redis,当用户登录检查会话是否存在,并删除它,然后创建一个新的,并将其保存到redis。一切都很好,直到我尝试使用siege对我的服务器进行压力测试。我模拟了1000次同时登录尝试,我看到一些会话没有被清除,仍然在redis存储中。
允许一个用户有多个会话。我做错了什么?请在下面找到一些代码。
var FileStreamRotator = require('file-stream-rotator'),
app = require('express')(),
fs = require("fs"),
bodyParser = require('body-parser'),
config = require("./providers/config"),
morgan = require('morgan'), //HTTP request logger middleware for node.js
cookieParser = require('cookie-parser'),
redis = require('redis'),
session = require('express-session'),
redisStore = require('connect-redis')(session),
publicRouter = require('./routes/public.js')();
var port = process.env.PORT || config.port;
var client = redis.createClient();
app.disable('x-powered-by');
app.use(cookieParser(config.session.secret));
app.use(session(
{
secret: config.session.secret,
store: new redisStore({host: config.redis.host, port: config.redis.port, client: client}),
saveUninitialized: false, // don't create session until something stored,
resave: false // don't save session if unmodified
}
));
app.use(morgan('combined', {stream: accessLogStream}));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
*****
app.all('/api/*', [require('./middlewares/validateRequest')]);
******
app.use('/api/public', publicRouter);
******
app.listen(port, function (err) {
if (!err) console.log('Find me on port ' + port + ' and say "Hello"');
else console.log(err);
});
auth.js
var User = require('./../models/user.js');
var Promise = require('bluebird');
var redis = require("./../providers/redis.js");
var util = require('util');
var auth = {
login: function (req, res) {
var login = req.body.login || '';
var password = req.body.password || '';
if (login === '') {
res.status(401);
res.json({
"status": 401,
"message": "login required"
});
return;
}
if (password === '') {
res.status(401);
res.json({
"status": 401,
"message": "password required"
});
return;
}
User.login(login, password)
.then(function (user) {
if (!user) {
res.status(401);
res.json({
"status": 401,
"message": "Incorrect login data."
});
}
return redis.get(util.format("usersess:%s", user.id))
.then(function (currentSession) {
if (currentSession === null) {
redis.set(util.format("usersess:%s", user.id), req.session.id)
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
});
} else {
if (currentSession !== req.session.id) {
return redis.del(util.format("sess:%s", currentSession))
.then(function () {
return redis.set(util.format("usersess:%s", user.id), req.session.id);
})
.then(function () {
delete user.password;
req.session.user = user;
res.json({
"status": 200,
"message": "User successfully logged in."
});
})
} else {
res.json({
"status": 200,
"message": "User successfully logged in."
});
}
}
})
})
.catch(function (err) {
console.log(err);
res.status(500);
res.json({
error: true,
data: {
message: err.message
}
});
});
},
logout: function (req, res) {
req.session.destroy(function (err) {
if (err) {
console.log("Can't destroy the session. See details below");
console.log(err);
res.status(500);
res.json({
"status": 500,
"message": err.message
})
} else {
res.status(200);
res.json({
"status": 200,
"message": "User successfully logged out."
})
}
});
}
};
module.exports = auth;
user .js
var Promise = require('bluebird'),
bcrypt = Promise.promisifyAll(require('bcrypt')),
db = require("./../providers/db.js");
var User = {
tableName: 'users',
login: function (login, password) {
if (!login || !password) throw new Error('login and password are both required');
return db.execStoredProcedure("user_get_by_login", [login.trim()])
.then(
function (rows) {
var user = rows[0][0];
return bcrypt.compareAsync(password, user.password)
.then(function (res) {
if (!res) user = null;
return user;
});
}
);
}
};
module.exports = User;
redis provider redis.js
var config = require('./../providers/config');
var Promise = require("bluebird"),
redis = require('promise-redis')(function(resolver) {
return new Promise(resolver);
}),
redisClient = redis.createClient(config.redis.port, config.redis.host),
util = require('util');
redisClient.on('connect', function () {
console.log(util.format('redis connected on %s:%s', config.redis.host, config.redis.port));
});
module.exports = redisClient;
我无法找到某些会话未被删除的确切原因,但经过大量调试和日志调查后,我认为这是由于节点异步性质。虽然mySQL获取操作需要一些时间,但一些登录操作可以并行运行,并为当前用户session_id获取相同的值。为了解决这个问题,我创建了中间件来检查当前用户会话id是否在redis存储中,如果不是——它只是破坏会话并注销用户要求新的登录尝试。这可能不是一个好的解决方案,但它完全解决了原来的问题。
相关文章:
- 在指令控制器中使用$attrs时出现问题
- 将PHP变量传递给jQuery时遇到问题
- ES6构造函数返回基类的实例
- 谷歌地图重叠MarkerSpiderfier实例化问题
- 使用ng repeat添加多个视图实例时的范围界定问题
- Unity 2d实例化鼠标位置问题,请告知
- 在 JavaScript 中修饰实例方法时出现问题
- 在 Unity 5.3 中向新实例化的游戏对象添加强制时出现问题
- 在EmberJS中创建模型实例的问题
- 在FF和IE中存在多个jPlayer实例的问题
- 初始化问题和jquery实例的使用
- 谷歌图表可视化实例化问题仪表板
- 在一个页面上使用多个jQuery UI控件实例有问题.手风琴控件的多种用途
- 访问实例方法时出现的JavaScript OO问题
- 无法获取具有相同参数的多个实例的问题列表
- Node.js express-session + redis单实例问题
- 作用域问题:Angular指令的多个实例具有隔离作用域
- JavaScript闭包问题:为什么第二次调用outerFn会创建一个新的outerVar实例?
- JavaScript闭包问题:为什么第二次调用outerFn会创建一个新的outerVar实例?
- 字符串变量实例的问题