lio*_*rix 4 race-condition redis node.js express
我正在使用带有connect-redis的nodej来存储会话数据.
我在会话中保存用户数据,并在会话生命周期中使用它.
我注意到在两个更改会话数据的请求之间可能存在竞争条件.
我曾尝试使用redis-lock锁定会话,但这对我来说有点问题.
我不想锁定整个会话,而只是锁定特定的会话变量.
我觉得这是不可能的,我想到了解决它的方向:
停止使用会话对象存储用户数据,并在使用之前将变量直接保存在redis中并锁定.
我知道它可以工作,但它需要我手动管理所有对象,而不是只通过会话对象访问redis.
你能和我分享最佳实践和建议吗?
谢谢,Lior
那么,实现自己的存储可能是您的选择.该文件显示,所有你需要做的是实现三个方法:.get,.set和.destroy(参见最后一段).它会是这样的(使用node-redis库并稍微修改原始的connect-redis存储):
var redis = require("redis"),
redis_client = redis.createClient(),
session_prefix = 'session::',
lock_suffix = '::lock',
threshold = 5000,
wait_time = 250,
oneDay = 86400;
/* If timeout is greater then threshold, then we assume that
one of the Redis Clients is dead and he cannot realese
the lock. */
function CustomSessionStore(opts) {
opts = opts || {};
var self = this;
self.ttl = opts.ttl; // <---- used for setting timeout on session
self.lock = function(sid, callback) {
callback = callback || function(){};
var key = session_prefix + sid + lock_suffix;
// try setting the lock with current Date
redis_client.setnx(key, Date.now( ), function(err, res) {
// some error handling?
if (res) {
// Everything's fine, call callback.
callback();
return;
}
// setnx failed, look at timeout
redis_client.get(key, function(err, res) {
// some error handling?
if (parseInt(res) + threshold > Date.now( )) {
// timeout, release the old lock and lock it
redis_client.getset(key, Date.now( ), function(err, date) {
if (parseInt(date) + threshold > Date.now()) {
// ups, some one else was faster in acquiring lock
setTimeout(function() {
self.lock(sid, callback);
}, wait_time);
return;
}
callback();
});
return;
}
// it is not time yet, wait and try again later
setTimeout(function() {
self.lock(sid, callback);
}, wait_time);
});
});
};
self.unlock = function(sid, callback) {
callback = callback || function(){};
var key = session_prefix + sid + lock_suffix;
redis_client.del(key, function(err) {
// some error handling?
callback();
});
};
self.get = function(sid, callback) {
callback = callback || function(){};
var key = session_prefix + sid;
// lock the session
self.lock(sid, function() {
redis_client.get(key, function(err, data) {
if (err) {
callback(err);
return;
}
try {
callback(null, JSON.parse(data));
} catch(e) {
callback(e);
}
});
});
};
self.set = function(sid, data, callback) {
callback = callback || function(){};
try {
// ttl used for expiration of session
var maxAge = sess.cookie.maxAge
, ttl = self.ttl
, sess = JSON.stringify(sess);
ttl = ttl || ('number' == typeof maxAge
? maxAge / 1000 | 0
: oneDay);
} catch(e) {
callback(e);
return;
}
var key = session_prefix + sid;
redis_client.setex(key, ttl, data, function(err) {
// unlock the session
self.unlock(sid, function(_err) {
callback(err || _err);
});
});
};
self.destroy = function(sid, callback) {
var key = session_prefix + sid;
redis_client.del(key, function(err) {
redis_client.unlock(sid, function(_err) {
callback(err || _err);
});
});
};
}
Run Code Online (Sandbox Code Playgroud)
旁注:我没有为.lock和实现错误处理.unlock.我要把它留给你了!:)可能会有一些小错误(我目前没有NodeJS,而且我正在记忆中写这个:D),但是你应该理解这个想法.这是包含有关如何使用锁定/解锁Redis 的讨论的链接setnx.
另一个注意事项:您可能希望为路由进行一些自定义错误处理,因为如果任何路由抛出异常,则Redis会话将不会被解锁.该.set方法总是被称为路由中的最后一个东西 - 与在路径.get开始时Express调用的方法相反(这就是我锁定.get和解锁的原因.set).你仍然会被锁定5秒钟,所以它不一定是个问题.请记住,它调整到您的需求(尤其是threshold和wait_time变量).
最后注意事项:使用此机制,您的请求处理程序将只为每个用户一个接一个地触发.这意味着,您将无法为每个用户运行并发处理程序.这可能是一个问题,因此另一个想法是将数据保存在会话之外并手动处理锁定/解锁.毕竟,有些东西必须手动处理.
我希望它有所帮助!祝好运!
| 归档时间: |
|
| 查看次数: |
1599 次 |
| 最近记录: |