2016-01-19 24 views
7

Ho un requisito per la generazione di un contatore che verrà inviato ad alcune chiamate API. La mia applicazione è in esecuzione su più nodi, quindi alcuni come volevo generare un contatore unico. Ho provato seguente codiceIncremento rosso distribuito con blocco

public static long GetTransactionCountForUser(int telcoId) 
    { 
     long valreturn = 0; 
     string key = "TelcoId:" + telcoId + ":Sequence"; 
     if (Muxer != null && Muxer.IsConnected && (Muxer.GetDatabase()) != null) 
     { 
      IDatabase db = Muxer.GetDatabase(); 
      var val = db.StringGet(key); 
      int maxVal = 999; 
      if (Convert.ToInt32(val) < maxVal) 
      { 
       valreturn = db.StringIncrement(key); 
      } 
      else 
      { 
       bool isdone = db.StringSet(key, valreturn); 
       //db.SetAdd(key,new RedisValue) .StringIncrement(key, Convert.ToDouble(val)) 
      } 
     } 
     return valreturn; 
    } 

e collaudate tramite Task libray parallelo. Quando ho valori limite ciò che vedo è che più tempo 0 voce è impostata

Per favore fatemi sapere cosa correzione che dovevo fare

Aggiornamento: La mia logica finale è come seguendo

public static long GetSequenceNumberForTelcoApiCallViaLuaScript(int telcoId) 
    { 
     long valreturn = 0; 
     int maxIncrement = 9999;//todo via configuration 
     if (true)//todo via configuration 
     { 
      IDatabase db; 
      string key = "TelcoId:" + telcoId + ":SequenceNumber"; 
      if (Muxer != null && Muxer.IsConnected && (db = Muxer.GetDatabase()) != null) 
      { 
       valreturn = (int)db.ScriptEvaluate(@" 
        local result = redis.call('incr', KEYS[1]) 
        if result > tonumber(ARGV[1]) then 
        result = 1 
        redis.call('set', KEYS[1], result) 
        end 
        return result", new RedisKey[] { key }, flags: CommandFlags.HighPriority, values: new RedisValue[] { maxIncrement }); 
      } 
     } 
     return valreturn; 
    } 
+0

Perché non si utilizza una tabella semplice, con solo una colonna di identità, fare un inserto e utilizzare lo SCOPE_IDENTITY restituita() - questo dovrebbe restituire qualcosa di unico per tutto il tempo. –

+0

Volevo evitare db insertion/db round trip. Ho un supporto per Cache Via Redis che volevo ridurre fino al –

+0

@KamranShahid per favore non usare 'string.Format' per parametrizzarlo; Modificherò il mio esempio per mostrare il modo preferito –

risposta

10

Infatti, il tuo codice non è sicuro attorno al limite di rollover, perché stai facendo un "get", (latenza e pensiero), "set" - senza verificare che le condizioni nel tuo "get" continuino ad essere applicate. Se il server è occupato intorno voce 1000 sarebbe possibile ottenere tutti i tipi di uscite folli, comprese le cose come:

1 
2 
... 
999 
1000 // when "get" returns 998, so you do an incr 
1001 // ditto 
1002 // ditto 
0 // when "get" returns 999 or above, so you do a set 
0 // ditto 
0 // ditto 
1 

Opzioni:

  1. utilizzare le API di transazione e vincoli per rendere il vostro concorrenza logica -safe
  2. riscrivere la logica come uno script Lua tramite ScriptEvaluate

Ora, le transazioni Redis (per l'opzione 1) are hard. Personalmente, userei "2" - oltre ad essere più semplice per codificare e fare il debug, significa che hai solo 1 round trip e operazione, invece di "get, watch, get, multi, incr/set, exec/scarto "e un ciclo" Riprova dall'inizio "per tenere conto dello scenario di interruzione. Posso provare a scriverlo come Lua per te, se vuoi - dovrebbe essere di circa 4 righe.


Ecco l'implementazione Lua:

string key = ... 
for(int i = 0; i < 2000; i++) // just a test loop for me; you'd only do it once etc 
{ 
    int result = (int) db.ScriptEvaluate(@" 
local result = redis.call('incr', KEYS[1]) 
if result > 999 then 
    result = 0 
    redis.call('set', KEYS[1], result) 
end 
return result", new RedisKey[] { key }); 
    Console.WriteLine(result); 
} 

Nota: se avete bisogno di parametrizzare il massimo, si usa:

if result > tonumber(ARGV[1]) then 

e:

int result = (int)db.ScriptEvaluate(..., 
    new RedisKey[] { key }, new RedisValue[] { max }); 

(quindi ARGV[1] prende il valore da max)

È necessario capire che eval/evalsha (che è ciò che ScriptEvaluate chiamate) non sono in competizione con altri server richiede, quindi non cambia nulla tra il incr e l'eventuale set. Ciò significa che non abbiamo bisogno della complessa logica watch ecc.

Ecco lo stesso (credo!) Tramite l'API di transazione/vincolo:

static int IncrementAndLoopToZero(IDatabase db, RedisKey key, int max) 
{ 
    int result; 
    bool success; 
    do 
    { 
     RedisValue current = db.StringGet(key); 
     var tran = db.CreateTransaction(); 
     // assert hasn't changed - note this handles "not exists" correctly 
     tran.AddCondition(Condition.StringEqual(key, current)); 
     if(((int)current) > max) 
     { 
      result = 0; 
      tran.StringSetAsync(key, result, flags: CommandFlags.FireAndForget); 
     } 
     else 
     { 
      result = ((int)current) + 1; 
      tran.StringIncrementAsync(key, flags: CommandFlags.FireAndForget); 
     } 
     success = tran.Execute(); // if assertion fails, returns false and aborts 
    } while (!success); // and if it aborts, we need to redo 
    return result; 
} 

Complicato, eh?Il caso semplice successo qui è allora:

GET {key} # get the current value 
WATCH {key} # assertion stating that {key} should be guarded 
GET {key} # used by the assertion to check the value 
MULTI  # begin a block 
INCR {key} # increment {key} 
EXEC   # execute the block *if WATCH is happy* 

che è ... un po 'di lavoro, e coinvolge una stalla gasdotto sul multiplexer. I casi più complicati (assertion failure, watch failure, wrap-arounds) avrebbero un output leggermente diverso, ma dovrebbero funzionare.

+0

Puoi aiutarmi nella transazione all'interno di stackexchange.redis? Tra l'altro ho visto gli stessi valori nella finestra di output :) –

+0

@KamranShahid ha aggiunto un esempio di Lua funzionante; fammi sapere se non aiuta –

+0

Sono a livello di principianti in redis. È la prima volta che ho sentito parlare di questo script lua. Lasciatemi un po 'di google per vedere come posso mappare la mia logica a questo Lua scriptevaluate –

1

È possibile utilizzare WATCH command - in questo modo, se il valore cambia, si otterrà avvisato

+0

Qualche idea su come posso ottenerlo in stackexchange.redis api? –

+0

@KamranShahid prova come https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md – chester89

+0

Grazie a Chester. Lo proverò –