2013-04-03 12 views
9

Sto usando NodeJS + Express + Redis su RedisOnGo + node_redis come client. Mi aspetto un sacco di concorrenza, quindi cerco di provare WATCH. Questo esempio non conterrà Express, solo le cose necessarie.Redis GUARDA MULTI EXEC da un client

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

risultato Expecting: ottenere gli errori N in callback exec e arrivare finalmente "inc" variabile = 10-N.

Risultato inaspettato: ottenere 0 errori di callback exec ma alla fine ottenere "inc" variabile = 1.

orologio non funziona con il mio codice.

Ho trovato questa discussione redis and watch + multi allows concurrent users. Dicono che è a causa del solo cliente redis.

Quindi ho trovato questa discussione Should I create a new Redis client for each connection?. Dicono che la generazione di un nuovo client per ogni transazione "non è assolutamente raccomandabile". Mi sono perso.

Si noti inoltre che devo autenticarsi sul server Redis. Grazie in anticipo!

EDITION 1:

sono stato in grado di farlo funzionare utilizzando esempio Redis locale (quindi non uso client.auth) con la creazione di una nuova connessione client prima di ogni iterazione WATCH-MULTI-EXEC. Non sono sicuro che sia buono, ma i risultati ora sono accurati al 100%.

EDITION 2 ha reso il lavoro se creo una nuova connessione client prima di ogni iterazione WATCH-MULTI-EXEC e poi fare client.auth e aspetto client.on.

La domanda esiste ancora, è giusto che crei nuove connessioni client per ogni iterazione?

risposta

17

Il tuo risultato è del tutto prevedibile. E giustamente.

Ricordare che node.js è un'applicazione a thread. Node.js utilizza l'input-output asincrono, ma i comandi devono essere inviati in rosso in modo strettamente sequenziale "richiesta-risposta". Quindi il tuo codice e le tue richieste vengono eseguiti in modo strettamente parallelo mentre stai utilizzando una sola connessione al server redis.

Guardate il codice:

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

A conferma di ciò fare questo passi:

  1. Connetti a Redis ed eseguire monitor comando.
  2. eseguire la vostra applicazione node.js

L'uscita sarebbe

SET inc 0 
    WATCH inc 

    GET inc 
    .... get command more 9 times 

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

In modo da ottenere esattamente i risultati che avete scritto sopra: "ottenere 0 errori di callback exec ma alla fine sempre" inc "variabile = 1.".

È corretto creare nuove connessioni client per ogni iterazione?

Per questo esempio - sì, risolve il problema. In generale, dipende dal numero di query "concorrenti" che si desidera eseguire. Redis è ancora un thread in modo che questo "concorrente" significhi solo un modo per concorrere simultaneamente al motore di redis.

Ad esempio, se l'uso 2 collegamenti del monitor potrebbero dare qualcosa di simile:

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 

E 'molto importante capire come Redis eseguire i vostri comandi. Redis è single threaded, tutti i comandi da tutte le connessioni eseguite una ad una in fila. Redis non garantisce che il comando da una connessione venga eseguito in una riga (se qui è presente un'altra connessione) quindi è necessario MULTI se si vuole essere sicuri che i comandi eseguano un blocco (se necessario). Ma perché WATCH aveva bisogno? Guarda i miei comandi redis sopra. Puoi vedere che il comando proveniente da diverse connessioni è misto. E guardare ti permettono di gestire questo.

Questo splendidamente spiegato nel documentation. Per favore leggilo!

+0

Grazie, la risposta sembra essere abbastanza esplicativo e si hanno capito la mia domanda corretta. Se non ci sono più risposte in 2 giorni, bounty è tuo. – igorpavlov

1

Ho finalmente ottenuto la tua domanda.

Se si desidera testare WATCH per la concorrenza, penso che sia necessario modificare il codice. come sappiamo. WATCH monitora solo la modifica del valore, non ottiene il funzionamento del valore. quindi nel tuo codice corrente, tutto il tuo comando get verrà eseguito correttamente e riceverà 0, quindi imposterà inc a 1. tutto il valore impostato è lo stesso (1), quindi l'orologio non fallirà.

Nel caso, abbiamo bisogno di garantire non solo write operazione è protetta, ma anche read. prima di impostare inc, è necessario watch e modificare un'altra chiave che è come un blocco pessimistico e quindi è possibile ottenere e modificare inc. in questo modo, si assicurerà la tua aspettativa.

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

ho provato nel mio PC.

[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] Console [INFO] - [4, 'OK']

[2013-11-26 18: 51: 09.391] Console [INFO] - [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']

+0

Grazie per la risposta. Tuttavia, non copre il mio problema. Comprendo il processo di accodamento di Redis, il problema qui è abbastanza semplice: ho un singolo nodo e se mi collego a Redis una volta all'inizio dell'applicazione e provo a usare questa connessione per tutte le query - GUARDA non sembra funzionare. Se creo una nuova connessione ogni volta che eseguo la "transazione", WATCH funziona e in realtà controlla la chiave. Ma non voglio collegarmi a Redis ogni volta che ho bisogno di fare una richiesta. Quello è il problema. – igorpavlov

+0

Ti ho preso e ho aggiornato la mia risposta, per favore controlla. –

+0

Grazie ancora per il tuo tempo, ma ancora, non corretto. Nel codice ho scritto un commento "// so che posso usare rc.incr(), questo è solo per esempio". So che incr() è un'operazione atomica. MA, all'interno di questo codice ci possono essere molte diverse operazioni non atomiche come GET-> SET. Inoltre, non è necessario utilizzare WATCH con incr(). – igorpavlov

1

Se si desidera utilizzare le operazioni di transazione MULTI/atomiche, ma si vuole fare in modo da utilizzare una connessione condivisa, per quanto So che la tua unica opzione è usare LUA.

Io uso lo scripting LUA all'interno di redis per un numero di cose, e la cosa con LUA è che l'intero script verrà eseguito atomicamente, il che è abbastanza conveniente. Devi essere consapevole però che questo significa che se hai uno script LUA lento stai rendendo il rosso lento per tutti quelli che usano il tuo server.

Inoltre, quando si utilizza LUA anche se è possibile operare su chiavi diverse, tenere presente che se si utilizza più di una chiave nello script non sarà possibile utilizzare il cluster Redis una volta rilasciato. Ciò è dovuto al fatto che, quando si utilizza un cluster, le chiavi verranno distribuite a diversi processi Redis, pertanto lo script LUA potrebbe non avere accesso a tutti su un singolo server.

In ogni caso, il problema con il cluster redis sarebbe lo stesso quando si invia un MULTI, poiché a MULTI non è consentito impostare chiavi diverse in un cluster.

Cheers,

j

+0

LUA è sicuramente una soluzione qui, grazie. Non sono sicuro chi riceverà una taglia ora =) – igorpavlov

Problemi correlati