Redis WATCH MULTI EXEC由一个客户提供

时间:2013-04-03 00:38:39

标签: javascript node.js redis node-redis

我在RedisOnGo + node_redis上使用NodeJS + Express + Redis作为客户端。我期待很多并发,所以试图测试WATCH。这个例子不包含Express,只是必要的东西。

var redis = require("redis")
var rc = redis.createClient(config.redis.port, config.redis.host)

rc.auth(config.redis.hash, function(err) {
    if (err) {
        throw err
    }
})

rc.on('ready', function () {
    rc.set("inc",0)
    for(var i=1;i<=10;i++){
        rc.watch("inc")
        rc.get("inc",function(err,data){
            var multi = rc.multi()
            data++ // I do know I can use rc.incr(), this is just for example
            multi.set("inc",data)
            multi.exec(function(err,replies){
                console.log(replies)
            })
        })
    }
})

期待结果:在exec回调中获得N个错误,最后得到“inc”变量= 10-N。

意外结果:在exec回调中获得0错误但最终获得“inc”变量= 1。

Watch无法使用我的代码。

我找到了这个帖子redis and watch + multi allows concurrent users。他们说这是因为唯一的redis客户端。

然后我找到了这个帖子Should I create a new Redis client for each connection?。他们说为每笔交易生成一个新客户“绝对不推荐”。我迷路了。

请注意,我必须向Redis服务器进行身份验证。提前谢谢!

第1版:

通过在每次WATCH-MULTI-EXEC迭代之前创建新的客户端连接,我能够使用本地Redis实例(因此我不使用client.auth)。不确定它是否合适,但现在结果是100%准确。

第2版 如果我在每次WATCH-MULTI-EXEC迭代之前创建一个新的客户端连接,然后执行client.auth并等待client.on。

问题仍然存在,我是否可以为每次迭代创建新的客户端连接?

3 个答案:

答案 0 :(得分:17)

您的结果完全可以预测。这是正确的。

请记住 - node.js是一个线程应用程序。 Node.js使用异步输入输出,但命令应该在redis中严格按顺序发送“请求 - 响应”。因此,当您只使用一个连接到redis服务器时,您的代码和请求将严格并行执行。

看看你的代码:

rc.on('ready', function () {
    rc.set("inc",0)
    for(var i = 1; i <= 10; i++){
        rc.watch("inc")
        //10 times row by row call get function. It`s realy means that your written
        //in an asynchronous style code executed strict in series. You are using just
        //one connection - so all command would be executed one by one.
        rc.get("inc",function(err,data){
            //Your data variable data = 0 for each if request.
            var multi = rc.multi()
            data++ //This operation is not atomic for redis so your always has data = 1
            multi.set("inc",data) //and set it
            multi.exec(function(err,replies){
                console.log(replies) 
            })
        })
    }
})

要确认这样做,请执行以下步骤:

  1. 连接到redis并执行monitor命令。
  2. 运行node.js应用程序
  3. 输出为

        SET inc 0
        WATCH inc
    
        GET inc 
        .... get command more 9 times
    
        MULTI
        SET inc 1
        EXEC
        .... command block more 9 times
    

    这样你就可以得到你上面写的结果:“在exec回调中得到0个错误,但最终得到”inc“变量= 1。”。

    您是否可以为每次迭代创建新的客户端连接?

    对于这个样本 - 是的,它解决了你的问题。通常 - 它取决于您要运行多少“并发”查询。 Redis仍然是一个线程,所以这个“并发”意味着只需要并发命令批处理到redis引擎。

    例如,如果使用2个连接,则monitor可以提供如下内容:

     1 SET inc 0 //from 1st connection
     2 WATCH inc //from 1st connection
     3 SET inc 0 //from 2nd connection            
     4 GET inc //from 1nd connection            
     5 WATCH int //from 2nd connection       
     6 GET inc //from 2nd connection                 
     7 MULTI //from 1st connection           
     8 SET inc 1 //from 1st connection    
     9 MULTI //from 2nd connection           
    10 SET inc 1 //from 2nd connection           
    11 EXEC //from 1st failed becouse of 2nd connection SET inc 0 (line 3) 
            //was executed after WATCH (line 2) 
    12 EXEC //success becouse of MULTI from 1st connection was failed and SET inc 1 from first 
            //connection was not executed
    
    -------------------------------------------------------------------------------> time 
                   |   |    |  |   |     |     |    |   |     |    |         |
    connection 1  set watch | get  |     |   multi set  |     |   exec(fail) |
    connection 2          set    watch  get            multi set            exec
    

    了解redis如何执行命令非常重要。 Redis是单线程的,所有连接中的所有命令都是逐行执行的。 Redis不保证来自一个连接的命令将连续执行(如果此处存在另一个连接),所以如果要确保您的命令执行一个块(如果需要),则应该使用MULTI。但为什么需要WA​​TCH呢?看看我上面的redis命令。您可以看到来自不同连接的命令是混合的。并且手表允许您管理这个。

    这在documentation中得到了精美的解释。请阅读!

答案 1 :(得分:2)

我终于得到了你的问题。

如果您想测试 WATCH 的并发性,我认为您需要更改代码。据我们所知。 WATCH 仅监控值的更改,而不是获取值操作。因此,在您当前的代码中,所有get命令都将成功执行并获得0,然后他们会将inc设置为1。所有设定值都相同(1),因此观察不会失败。

在这种情况下,我们需要确保write操作不仅受到保护,还需要read。在设置inc之前,您需要watch并修改另一个作为悲观锁定的键,然后我们就可以获取并更改inc。通过这种方式,它将确保您的期望。

rc.set("inc",0)
for(var i=1;i<=10;i++){
    rc.watch("inc-lock")
    rc.get("inc",function(err,data){
        var multi = rc.multi()
        data++
        multi.incr("inc-lock")
        multi.set("inc",data)
        multi.exec(function(err,replies){
            console.log(replies)
        })
    })
}

我在电脑上测试了它。

  

[2013-11-26 18:51:09.389] [INFO] console - [1,'OK']

     

[2013-11-26 18:51:09.390] [INFO] console - [2,'OK']

     

[2013-11-26 18:51:09.390] [INFO] console - [3,'OK']

     

[2013-11-26 18:51:09.390] [INFO] console - [4,'OK']

     

[2013-11-26 18:51:09.391] [INFO] console - [5,'OK']

     

[2013-11-26 18:51:09.391] [INFO] console - [6,'OK']

     

[2013-11-26 18:51:09.392] [INFO] console - [7,'OK']

     

[2013-11-26 18:51:09.392] [INFO] console - [8,'OK']

     

[2013-11-26 18:51:09.393] [INFO] console - [9,'OK']

     

[2013-11-26 18:51:09.393] [INFO] console - [10,'OK']

答案 2 :(得分:1)

如果你想使用事务/原子MULTI操作但你想使用共享连接这样做,据我所知你唯一的选择就是使用LUA。

我在redis中使用LUA脚本来处理很多事情,LUA的事情是整个脚本将以原子方式执行,这非常方便。你必须要注意,这意味着如果你有一个缓慢的LUA脚本,那么使用你的服务器的每个人都会使redis变慢。

此外,即使您可以使用不同的密钥操作LUA,但请注意,如果您在脚本中使用多个密钥,则一旦发布,您将无法使用Redis群集。这是因为,在使用群集时,密钥将分发到不同的Redis进程,因此您的LUA脚本可能无法访问单个服务器上的所有密钥。

在任何情况下,发布MULTI时redis群集的问题都是相同的,因为不允许MULTI在群集上设置不同的密钥。

干杯,

Ĵ