sql >> Databáze >  >> NoSQL >> Redis

Redis distribuovaný přírůstek s aretací

Ve skutečnosti váš kód není bezpečný kolem hranice převrácení, protože provádíte „získání“, (latence a myšlení), „nastavení“ – aniž byste zkontrolovali, zda stále platí podmínky ve vašem „získání“. Pokud je server zaneprázdněn kolem položky 1000, bylo by možné získat nejrůznější bláznivé výstupy, včetně věcí jako:

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

Možnosti:

  1. použijte rozhraní API pro transakce a omezení, abyste zajistili souběžnost vaší logiky
  2. Přepište svou logiku jako skript Lua pomocí ScriptEvaluate

Nyní jsou transakce redis (podle možnosti 1) obtížné. Osobně bych použil "2" - kromě toho, že je jednodušší kódovat a ladit, znamená to, že máte pouze 1 zpáteční cestu a operaci, na rozdíl od "získat, sledovat, získat, více, incr/set, exec/ discard" a smyčka "opakovat od začátku" pro zohlednění scénáře přerušení. Můžu to zkusit napsat jako Lua, jestli chceš - mělo by to být asi 4 řádky.

Zde je implementace 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);
}

Poznámka:Pokud potřebujete parametrizovat maximum, použijte:

if result > tonumber(ARGV[1]) then

a:

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

(takže ARGV[1] přebírá hodnotu z max )

Je nutné pochopit, že eval /evalsha (což je to, co ScriptEvaluate volání) nekonkurují jiným požadavkům serveru , takže mezi incr se nic nemění a možnou set . To znamená, že nepotřebujeme složité watch atd.

Zde je totéž (myslím!) prostřednictvím rozhraní API pro transakce / omezení:

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

Složité, co? Jednoduchý případ úspěchu tady je pak:

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ž je... docela dost práce a zahrnuje to zablokování potrubí na multiplexeru. Složitější případy (selhání tvrzení, selhání hodinek, obtékání) by měly mírně odlišný výstup, ale měly by fungovat.



  1. Chyba MongoDB:Nelze použít opakovatelné zápisy s limitem=0

  2. MongoDB $weeklyUpdate #66 (22. dubna 2022):Hackathons, mongosh a Github

  3. Aby $elemMatch (projekce) vrátil všechny objekty, které odpovídají kritériím

  4. Mohu vytvořit dva sloupce, které jsou navzájem jedinečné? nebo použít složený primární klíč v redis?