4

我正在使用带有 connect-redis 的 nodejs 来存储会话数据。

我在会话中保存用户数据,并在会话生命周期中使用它。

我注意到在两个更改会话数据的请求之间可能存在竞争条件。

我尝试使用 redis-lock 来锁定会话,但这对我来说有点问题。

我不想锁定整个会话,而是只锁定特定的会话变量。

我发现这是不可能的,我想了解决它的方向:

停止使用session对象存储用户数据,直接将变量保存在redis中并在使用前加锁。

我知道它可以工作,但它需要我手动管理所有对象,而不是仅仅通过会话对象访问 redis。

您能否与我分享最佳实践和您的建议?

谢谢,里奥

4

1 回答 1

5

好吧,实现自己的存储可能是您的选择。该文档显示您需要做的就是实现三个方法:.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);
            });
        });
    };
}

旁注:我没有为.lockand实现错误处理.unlock。我把这个留给你!:) 可能会有一些小错误(我现在没有 NodeJS,我是凭记忆写的 :D),但你应该理解这个想法。这是包含有关如何使用锁定/解锁 Redis的讨论的链接。setnx

另一个注意事项:您可能希望对路由进行一些自定义错误处理,因为如果任何路由抛出异常,那么 Redis 会话将不会被解锁。该.set方法总是作为路由中的最后一件事被调用 - 与.getExpress 在路由一开始调用的方法相反(这就是我 lock at.get和 unlock at的原因.set)。你仍然只会被锁定 5 秒钟,所以它不一定是一个问题。请记住根据您的需要(尤其是变量)对其进行threshold调整wait_time

最后一点:使用这种机制,您的请求处理程序只会为每个用户一个接一个地触发。这意味着,您将无法为每个用户运行并发处理程序。这可能是一个问题,因此另一个想法是将数据保存在会话之外并手动处理锁定/解锁。毕竟,有些事情必须手动处理。

我希望它有帮助!祝你好运!

于 2012-07-10T22:19:46.950 回答