redis and watch + multi allows concurrent users

2019-06-05 16:44发布

问题:

I'm doing a load test on user signup with the same email address for a webservice and the first 10 users which connect simultaneously will always register.

I'm using WATCH and MULTI but that doesn't seem to work any way.

I'm calling save() to save the user.

this.insert = function(callback) {
    this.preInsert();

    created = new Date();
    updated = new Date();

    // Also with these uncommented it still doesn't work
    // Common.client.watch("u:" + this.username);
    // Common.client.watch("em:" + this.email);

    console.log(ID + " email is locked " + this.email);
    Common.client.multi()
    .set("u:" + this.username, ID)
    .hmset("u:" + ID, 
        {"username": this.username
        ,"password": this.password
        ,"email": this.email
        ,"payment_plan": payment_plan
        ,"created": created.getTime()
        ,"updated": updated.getTime()
        ,"avatar": this.avatar})
    .zadd("u:users", 0, ID)
    .sadd("u:emails", this.email)
    .set("u:"+ ID + ":stats", 0)
    .set("em:" + this.email, ID)
    .exec();

    this.postInsert();

    if (callback != null)
        callback(null, this);
}

this.save = function(callback) {
    // new user
    if (ID == -1) {
        var u = this;

        Common.client.watch("u:" + this.username);
        Common.client.exists("u:" + this.username, function(error, exists) {
            // This username already exists
            if (exists == 1) {
                Common.client.unwatch();
                if (callback != null)
                    callback({code: 100, message: "This username already exists"});
            }
            else {
                Common.client.watch("em:" + u.email);
                Common.client.get("em:" + u.email, function(err, emailExists) {
                    if (emailExists != null) {
                        Common.client.unwatch();
                        if (callback != null)
                            callback({code: 101, message: "This email is already in use"});
                    }
                    else {
                        Common.client.incr("u:nextID", function(error, id) {
                            if (error) callback(error);
                            else {
                                ID = id;
                                u.insert(callback);
                            } 
                        });
                    }
                });
            }
        });
    }
    // existing user
    else {
        var u = this;
        Common.client.get("em:" + this.email, function(err, emailExists) {
            if (emailExists != ID && emailExists) {
                if (callback != null) {
                    callback({code: 101, message: "This email is already in use " + ID + " " + emailExists});
                }
            }
            else {
                u.update(callback);
            }
        });
    }
}

The output almost always is:

1 email is locked test@test.com
2 email is locked test@test.com
3 email is locked test@test.com
4 email is locked test@test.com
5 email is locked test@test.com
6 email is locked test@test.com
7 email is locked test@test.com
8 email is locked test@test.com
9 email is locked test@test.com
10 email is locked test@test.com

Am I doing something wrong or redis can't handle that much concurrency. Also this is the definition of Common:

var Common = {
    client: redis.createClient(),
...
};

回答1:

YES! After a night's rest of course the solution came to me in the shower.

The problem was that I used a single redis thread for the whole app and all the connections registered the watch on that thread. Of course it didn't signal that the keys were modified by a different client because there was no other client.



回答2:

I know this thread is a 8 months old, but anyway, my thoughts still can help someone. There is a problem I still cannot understand, I even started my own thread dedicated to this issue Redis WATCH MULTI EXEC by one client, where I refer to yours. I now use "connection per transaction" method, which means I create new connection if I need to do transactions with WATCH-MULTI-EXEC. In other cases for atomic operations I use connection, which is created during an app launch. Not sure this method is effective, because creating a new connection means creating + authorizing and this produces latency, but it works.