2016-01-19 13 views
7

Ich habe eine Anforderung zum Generieren eines Zählers, die an einige API-Aufrufe gesendet werden. Meine Anwendung läuft auf mehreren Knoten, also einige, wie ich Unique Counter generieren wollte. Ich habe versucht, Code folgendeRedis verteiltes Inkrement mit Sperren

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

Und es via Task Parallel libray Funktionsprüfung unterzogen. Wenn ich Grenzwerte haben, was ich sehe, ist, dass mehrere Zeit 0 Eintrag

gesetzt Bitte lassen Sie mich wissen, welche Korrektur i

-Update tun musste: Meine letzte Logik als

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; 
    } 
finden
+0

benachrichtigt, warum Sie eine einfache Tabelle nicht mit nur einer Identitätsspalte verwenden, einen Einsatz tun und benutzen Sie wieder SCOPE_IDENTITY() - Das sollte immer wieder einzigartig sein. –

+0

Ich wollte db Einfügung/db Rundreise vermeiden. Ich habe eine Unterstützung für Cache Via Redis, die ich bis –

+0

@KamranShahid vollständig machen wollte bitte verwenden Sie 'string.Format' nicht, um das zu parametrisieren; Ich werde mein Beispiel bearbeiten, um den bevorzugten Weg zu zeigen –

Antwort

10

In der Tat ist Ihr Code nicht sicher um die Rollover-Grenze, weil Sie ein "Get", (Latenz und Denken), "Set" - ohne zu überprüfen, dass die Bedingungen in Ihrem "Get" immer noch gelten. Wenn der Server um Punkt 1000 beschäftigt ist, wäre es möglich, alle möglichen verrückten Ausgaben zu erhalten, einschließlich solcher Dinge wie:

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 

Optionen:

  1. die Transaktion und Constraint-APIs verwenden, um Ihre Logik Gleichzeitigkeit zu machen -safe
  2. umschreiben Ihre Logik als Lua Skript über ScriptEvaluate

Jetzt redis Transaktionen (pro Option 1) are hard. Persönlich würde ich "2" verwenden - zusätzlich zu dem einfacheren Programmieren und Debuggen bedeutet dies, dass Sie nur eine Hin- und Rückfahrt haben, im Gegensatz zu "erhalten, schauen, holen, multi, incr/set, exec/Verwerfen ", und eine" Retry from Start "-Schleife, um das Abbruchszenario zu berücksichtigen. Ich kann versuchen, es als Lua für Sie zu schreiben, wenn Sie möchten - es sollte ungefähr 4 Zeilen sein.


Hier ist die Lua Umsetzung:

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

Hinweis: Wenn Sie die max parametrieren müssen, verwenden Sie:

if result > tonumber(ARGV[1]) then 

und:

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

(so ARGV[1] nimmt den Wert von max)

Es ist notwendig zu verstehen, dass eval/evalsha (was ScriptEvaluate Anrufe) nicht mit anderen Server im Wettbewerb fordert, so ändert sich nichts zwischen dem incr und der möglichen set. Dies bedeutet, dass wir keine komplexe watch usw. Logik benötigen.

Hier ist das gleiche (ich glaube!) Über die Transaktion/Constraint API:

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

kompliziert, nicht wahr?Der einfache Erfolgsfall hier ist dann:

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* 

das ist ... ziemlich viel Arbeit, und beinhaltet eine Pipeline Stall auf den Multiplexer. Die komplizierteren Fälle (Assertionsfehler, Watch-Fehler, Wrap-arounds) hätten etwas andere Ausgaben, sollten aber funktionieren.

+0

Können Sie mir bei der Transaktion in stackexchange.redis helfen? Übrigens habe ich dieselben Werte im Ausgabefenster gesehen :) –

+0

@KamranShahid hat ein funktionierendes Lua-Beispiel hinzugefügt; lass es mich wissen, wenn es nicht hilft –

+0

Ich bin auf sehr Anfänger Niveau in redis.It ist das erste Mal, dass ich über dieses Lua-Skript gehört habe. Lassen Sie mich ein wenig googeln, um zu sehen, wie ich meine Logik zu diesem Lua scriptevaluate –

1

können Sie WATCH command verwenden - auf diese Weise, wenn sich der Wert ändert, werden Sie

+0

Irgendeine Idee, wie ich es in stackexchange.redis api bekommen kann? –

+0

@KamranShahid versuchen smth wie https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Transactions.md – chester89

+0

Danke Chester. Ich werde es versuchen –