Redis
 sql >> Baza danych >  >> NoSQL >> Redis

Redis rozproszony przyrost z blokowaniem

Rzeczywiście, twój kod nie jest bezpieczny wokół granicy najazdu, ponieważ wykonujesz „pobieranie” (opóźnienie i myślenie), „ustawianie” — bez sprawdzania, czy warunki w twoim „pobierz” nadal mają zastosowanie. Jeśli serwer jest zajęty w okolicach pozycji 1000, możliwe byłoby uzyskanie różnego rodzaju zwariowanych danych wyjściowych, w tym rzeczy takich jak:

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

Opcje:

  1. użyj interfejsów API transakcji i ograniczeń, aby zapewnić bezpieczną współbieżność logiki
  2. przepisz swoją logikę jako skrypt Lua za pomocą ScriptEvaluate

Teraz transakcje redis (na opcję 1) są trudne. Osobiście użyłbym "2" - oprócz tego, że jest prostsze w kodowaniu i debugowaniu, oznacza to, że masz tylko 1 podróż w obie strony i operację, w przeciwieństwie do "get, watch, get, multi, incr/set, exec/ odrzuć” i pętlę „ponów próbę od początku”, aby uwzględnić scenariusz przerwania. Mogę spróbować napisać to jako Lua, jeśli chcesz - powinno to być około 4 linijek.

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

Uwaga:jeśli potrzebujesz sparametryzować maksimum, użyjesz:

if result > tonumber(ARGV[1]) then

i:

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

(więc ARGV[1] pobiera wartość z max )

Należy zrozumieć, że eval /evalsha (co jest tym, co ScriptEvaluate połączeń) nie konkurują z innymi żądaniami serwera , więc nic się nie zmienia między incr i możliwy set . Oznacza to, że nie potrzebujemy skomplikowanego watch logika itp.

Oto to samo (chyba!) za pośrednictwem interfejsu API transakcji / ograniczeń:

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

Skomplikowane, co? Prosty przypadek sukcesu oto więc:

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*

co jest… dość pracochłonne i wiąże się z zatrzymaniem potoku na multiplekserze. Bardziej skomplikowane przypadki (niepowodzenia asercji, awarie zegarków, zawijanie) miałyby nieco inne wyniki, ale powinny działać.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Redis:Amazon EC2 kontra Elasticache

  2. Redis publikuj-subskrybuj:czy Redis gwarantuje dostarczenie wiadomości nawet przy ogromnym stresie?

  3. Jak włączyć rozproszoną/klastrową pamięć podręczną podczas korzystania z redis z wiosenną pamięcią podręczną danych?

  4. Przełączanie awaryjne Redis za pomocą StackExchange / Sentinel z C#

  5. Czy redis na Heroku jest możliwy bez dodatku?