sql >> Database teknologi >  >> NoSQL >> Redis

Redis distribueret trin med låsning

Faktisk er din kode ikke sikker omkring rollover-grænsen, fordi du laver en "get", (latency og tænkning), "set" - uden at kontrollere, at betingelserne i din "get" stadig gælder. Hvis serveren er optaget omkring punkt 1000, ville det være muligt at få alle mulige skøre output, inklusive ting som:

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

Valgmuligheder:

  1. brug transaktions- og begrænsnings-API'erne til at gøre din logik samtidig sikker
  2. omskriv din logik som et Lua-script via ScriptEvaluate

Nu er gendis-transaktioner (pr. mulighed 1) svære. Personligt ville jeg bruge "2" - udover at det er nemmere at kode og fejlfinde, betyder det, at du kun har 1 rundtur og betjening, i modsætning til "get, watch, get, multi, incr/set, exec/ kassere", og en "forsøg igen fra start"-løkke for at tage højde for afbrydelsesscenariet. Jeg kan prøve at skrive det som Lua for dig, hvis du vil - det skal være omkring 4 linjer.

Her er Lua-implementeringen:

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

Bemærk:Hvis du skal indstille maks., skal du bruge:

if result > tonumber(ARGV[1]) then

og:

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

(altså ARGV[1] tager værdien fra max )

Det er nødvendigt at forstå den eval /evalsha (hvilket er hvad ScriptEvaluate opkald) konkurrerer ikke med andre serveranmodninger , så intet ændrer sig mellem incr og det mulige set . Det betyder, at vi ikke har brug for kompleks watch osv. logik.

Her er det samme (tror jeg!) via transaktions-/begrænsnings-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;
}

Kompliceret, ikke? Den enkle successag her er så:

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*

hvilket er... en del arbejde, og involverer en pipeline stall på multiplexeren. De mere komplicerede sager (påstandsfejl, urfejl, omslutninger) ville have lidt anderledes output, men burde fungere.



  1. Sådan forbinder du mongodb-klienter til lokale Meteor MongoDB

  2. Hvordan udfører man en masseopdatering af dokumenter i MongoDB med Java?

  3. Installation og konfiguration af Redis på Ubuntu

  4. Introduktion til Apache HBase Snapshots, del 2:Deeper Dive