Redis WATCH MULTI EXEC одним клиентом

Я использую NodeJS + Express + Redis на RedisOnGo + node_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 ошибок в обратных вызовах exec и, наконец, получение переменной " inc " = 10-N.

неожиданный результат: получение 0 ошибок в обратных вызовах exec, но, наконец, получение переменной " inc " = 1.

посмотреть не работать с моим кодом.

Я нашел этот поток redis и watch + multi позволяет одновременным пользователям. Они говорят, что это из-за единственного клиента Redis.

затем я нашел этот поток должен ли я создать новый клиент Redis для каждого соединения?. Они говорят, что генерировать нового клиента для каждой транзакции "определенно не рекомендуется". Я заблудился.

также обратите внимание, что я должен аутентифицироваться на сервере Redis. Спасибо вперед!

издание 1:

я смог заставить его работать с помощью локального экземпляра Redis (поэтому я не использую client.auth), создавая новое клиентское соединение перед каждой итерацией WATCH-MULTI-EXEC. Не уверен, что это хорошо, но результаты теперь на 100% точны.

издание 2 Сделал это, если я создаю новое клиентское соединение перед каждой итерацией WATCH-MULTI-EXEC, а затем делаю клиент.двиг и ждать клиента.на.

в вопрос все еще существует, нормально ли создавать новые клиентские соединения для каждой итерации?

3 ответов


ваш результат вполне предсказуем. И правильно сделал.

имейте в виду - узел.js - это одно потоковое приложение. Узел.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. запустите узел.приложения на JS

выход будет

    SET inc 0
    WATCH inc

    GET inc 
    .... get command more 9 times

    MULTI
    SET inc 1
    EXEC
    .... command block more 9 times

так что вы получите именно те результаты, которые вы написали выше: "получение 0 ошибок в обратных вызовах exec, но, наконец, получение переменной "inc" = 1.".

нормально ли создавать новые клиентские соединения для каждой итерации?

для этого образца-Да, это решает вашу проблему. В общем - это зависит от того, сколько "параллельных" запросов вы хотите запустить. Redis по-прежнему является одним потоком, поэтому этот "параллельный" означает только способ одновременного пакета команд для Redis engine.

например, если использовать 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 не гарантирует, что команда от одного соединение будет выполняться подряд (если здесь присутствуют другие соединения), поэтому ваш должен быть мульти, если хотите, чтобы ваши команды выполняли один блок (если это необходимо). Но зачем нужны часы? Посмотрите на мои команды redis выше. Вы можете видеть, что команды, поступающие из разных соединений, смешаны. И часы позволяют вам управлять этим.

это прекрасно объясняется в документация. Пожалуйста, прочтите!


я получил ваш вопрос, наконец.

если вы хотите проверить посмотреть для параллелизма, я думаю, вам нужно изменить свой код. как известно. посмотреть только контролируйте изменение значения, не получая деятельность значения. Итак, в вашем текущем коде все ваши get команда будет выполнена успешно и get 0, тогда они будут inc to 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] консоль - [ 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']


Если вы хотите использовать transaction / atomic MULTI operations, но вы хотите сделать это с помощью общего соединения, насколько я знаю, ваш единственный вариант-использовать LUA.

Я использую Lua-скрипты в redis для ряда вещей, и дело с LUA в том, что весь скрипт будет выполняться атомарно, что довольно удобно. Вы должны знать, что это означает, что если у вас медленный сценарий LUA, вы делаете redis медленным для всех, кто использует ваш сервер.

кроме того, при использовании LUA даже если вы можете работать с разными ключами, помните, что если вы используете более одного ключа в своем скрипте, вы не сможете использовать кластер Redis после его освобождения. Это связано с тем, что при использовании кластера ключи будут распределены между различными процессами Redis, поэтому ваш сценарий LUA может не иметь доступа ко всем из них на одном сервере.

в любом случае проблема с кластером redis будет такой же при выдаче MULTI, так как MULTI не будет разрешено устанавливать разные ключи на группа.

спасибо,

j