Redis WATCH MULTI EXEC由一个客户端

Redis WATCH MULTI EXEC by one client

本文关键字:一个 客户端 WATCH MULTI EXEC Redis      更新时间:2023-09-26

我在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)
            })
        })
    }
})

期望结果:在执行回调中得到N个错误,最终得到"inc"变量= 10-N.

意外结果:在exec回调中得到0错误,但最终得到"inc"变量= 1.

手表不适合我的代码。

我发现这个线程redis和watch + multi允许并发用户。他们说这是因为只有redis客户端。

然后我发现这个线程我应该为每个连接创建一个新的Redis客户端吗?他们表示,"绝对不建议"为每笔交易生成一个新客户。我迷路了。

还请注意,我必须认证到Redis服务器。提前感谢!

版1:

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

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

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

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

记住- 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

这样你就得到了你上面写的结果:"在执行回调中得到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命令。您可以看到来自不同连接的命令是混合的。手表可以让你处理这个问题。

这在文档中有很好的解释。请读一读!

我终于得到了你的问题。

如果你想测试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][信息]控制台-[1,'好的']

[2013-11-26 18:51:09.390][信息]控制台-(2,'好的']

[2013-11-26 18:51:09.390][信息]控制台-[3,'好的']

[2013-11-26 18:51:09.390][信息]控制台-(4 '好的']

[2013-11-26 18:51:09.391][信息]控制台-[5,'好的']

[2013-11-26 18:51:09.391][信息]控制台——(6 '好的']

[2013-11-26 18:51:09.392][信息]控制台-[7,'好的']

[2013-11-26 18:51:09.392][信息]控制台-(8 '好的']

[2013-11-26 18:51:09.393][信息]控制台-(9,"OK")

[2013-11-26 18:51:09.393][信息]控制台——(10,"OK")

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

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

另外,当使用LUA时,即使你可以操作不同的键,也要注意,如果你在脚本中使用多个键,一旦它被释放,你将无法使用Redis集群。这是因为,当使用集群时,密钥将被分发到不同的Redis进程,所以你的LUA脚本可能无法访问单个服务器上的所有密钥。

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

欢呼,j

我发现executeIsolated(callback)通过

https://github.com/redis/node-redis/blob/master/docs/isolated-execution.md

似乎是正确的解决方案。

相关文章: