我在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。
问题仍然存在,我是否可以为每次迭代创建新的客户端连接?
答案 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)
})
})
}
})
要确认这样做,请执行以下步骤:
monitor
命令。输出为
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中得到了精美的解释。请阅读!
答案 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在群集上设置不同的密钥。
干杯,
Ĵ