10

我在 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 和 watch + multi 允许并发用户。他们说这是因为唯一的redis客户端。

然后我发现这个线程我应该为每个连接创建一个新的Redis客户端吗?. 他们说“绝对不推荐”为每笔交易生成一个新客户。我搞不清楚了。

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

第 1 版:

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

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

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

4

3 回答 3

22

你的结果是完全可以预测的。没错。

请记住 - 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 应用程序

输出将是

    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。但是为什么需要 WATCH 呢?看看我上面的redis命令。您可以看到来自不同连接的命令是混合的。并且手表可以让你管理这个。

这在文档中得到了很好的解释。请阅读!

于 2013-11-25T06:51:39.313 回答
2

我终于明白了你的问题。

如果您想测试WATCH的并发性,我认为您需要更改代码。据我们所知。WATCH只监控值的变化,不获取值操作。因此,在您当前的代码中,您的所有get命令都将成功执行并 get 0,然后它们将设置inc1. 所有设置值都相同(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] 控制台 - [1, 'OK']

[2013-11-26 18:51:09.390] [INFO] 控制台 - [2, 'OK']

[2013-11-26 18:51:09.390] [INFO] 控制台 - [3, 'OK']

[2013-11-26 18:51:09.390] [INFO] 控制台 - [4, 'OK']

[2013-11-26 18:51:09.391] [INFO] 控制台 - [5, 'OK']

[2013-11-26 18:51:09.391] [INFO] 控制台 - [6, 'OK']

[2013-11-26 18:51:09.392] [INFO] 控制台 - [7, 'OK']

[2013-11-26 18:51:09.392] [INFO] 控制台 - [8, 'OK']

[2013-11-26 18:51:09.393] [INFO] 控制台 - [9, 'OK']

[2013-11-26 18:51:09.393] [INFO] 控制台-[10,'OK']

于 2013-11-26T08:46:13.483 回答
1

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

我在redis中使用LUA脚本来做很多事情,而LUA的事情是整个脚本将原子执行,这非常方便。你必须意识到,这意味着如果你有一个缓慢的 LUA 脚本,你会让使用你服务器的每个人的 redis 变慢。

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

无论如何,在发出 MULTI 时,redis 集群的问题是相同的,因为不允许 MULTI 在集群上设置不同的键。

干杯,

j

于 2013-11-26T12:19:51.403 回答