一个客户端的 Redis WATCH MULTI EXEC
Posted
技术标签:
【中文标题】一个客户端的 Redis WATCH MULTI EXEC【英文标题】:Redis WATCH MULTI EXEC by one client 【发布时间】:2013-03-24 12:07:58 【问题描述】:我在 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,它就可以工作。
问题依然存在,每次迭代都创建新的客户端连接可以吗?
【问题讨论】:
【参考方案1】:您的结果是完全可以预测的。没错。
请记住 - 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)
)
)
)
要确认这一点,请执行以下步骤:
-
连接redis并执行
monitor
命令。
运行您的 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命令。您可以看到来自不同连接的命令是混合的。并且手表可以让你管理这个。
这在documentation 中有很好的解释。请阅读!
【讨论】:
谢谢,您的回答似乎很有解释性,您已经正确理解了我的问题。如果 2 天内没有更多答案,赏金就是你的了。【参考方案2】:我终于明白你的问题了。
如果您想测试 WATCH 的并发性,我认为您需要更改代码。据我们所知。 WATCH只监控值的变化,不进行取值操作。所以在你当前的代码中,你所有的get
命令都会成功执行并得到0
,然后他们会将inc
设置为1
。所有设置的值都是一样的(1
),所以watch不会失败。
在这种情况下,我们需要确保不仅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']
【讨论】:
感谢您的回答。但是,它并没有涵盖我的问题。我了解 Redis 的排队过程,但这里的问题很简单:我有一个单节点进程,如果我在应用程序启动时连接到 Redis 一次并尝试将此连接用于所有查询 - WATCH 不会似乎工作。如果我每次执行“事务”时都创建新连接,则 WATCH 工作并实际监视密钥。但是我不想每次需要向 Redis 发出请求时都连接到它。这就是问题所在。 再次感谢您的宝贵时间,但仍然不正确。在代码中,我写了一条注释“//我知道我可以使用 rc.incr(),这只是示例”。我知道 incr() 是一个原子操作。但是,在这段代码中可能有很多不同的非原子操作,比如 GET->SET。此外,您不需要将 WATCH 与 incr() 一起使用。 我猜你不明白我的代码是什么意思。我增加了用于添加手表锁的 inc-lock 键。我们真正的公司逻辑并不重要。两件事情。顺便说一句,我的代码在集群环境中会发出错误,这会产生真正的竞争条件。它将证明“手表”是安全的 我已经用这个额外的命令执行了你的代码:setTimeout(function() rc.get("inc",function(err,data) console.log('inc value: ' + data); ); ,3000);
结果是 [ 1, 'OK' ] [ 2, 'OK' ] [ 3, 'OK' ] [ 4, 'OK' ] [ 5, 'OK' ] [ 6, 'OK' ] [ 7, 'OK' ] [ 8, 'OK' ] [ 9, 'OK' ] [ 10, 'OK' ] 公司值:1我期待 inc 值:10【参考方案3】:
如果您想使用事务/原子 MULTI 操作,但又想使用共享连接,据我所知,您唯一的选择是使用 LUA。
我在redis中使用LUA脚本做很多事情,而LUA的事情是整个脚本将原子执行,这很方便。您必须注意,这意味着如果您的 LUA 脚本速度较慢,那么使用您服务器的每个人都会使 redis 变慢。
另外,在使用 LUA 时,即使您可以对不同的键进行操作,但请注意,如果您在脚本中使用多个键,则一旦发布,您将无法使用 Redis 集群。这是因为,在使用集群时,密钥将被分发到不同的 Redis 进程,因此您的 LUA 脚本可能无法访问单个服务器上的所有密钥。
无论如何,在发出 MULTI 时,redis 集群的问题是相同的,因为不允许 MULTI 在集群上设置不同的键。
干杯,
j
【讨论】:
LUA 在这里绝对是一个解决方案,谢谢。不确定现在谁会收到赏金=)以上是关于一个客户端的 Redis WATCH MULTI EXEC的主要内容,如果未能解决你的问题,请参考以下文章