Redis
 sql >> база данни >  >> NoSQL >> Redis

Redis разпределено увеличение със заключване

Всъщност вашият код не е безопасен около границата на преобръщане, защото правите "get", (латентност и мислене), "set" - без да проверявате дали условията във вашето "get" все още се прилагат. Ако сървърът е зает около елемент 1000, би било възможно да получите всякакви луди резултати, включително неща като:

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

Опции:

  1. използвайте приложните програмни интерфейси (API) на транзакциите и ограниченията, за да направите своята логика безопасна за паралелност
  2. пренапишете логиката си като Lua скрипт чрез ScriptEvaluate

Сега транзакциите на redis (за опция 1) са трудни. Лично аз бих използвал "2" - освен че е по-лесно за кодиране и отстраняване на грешки, това означава, че имате само 1 двупосочно пътуване и операция, за разлика от "get, watch, get, multi, incr/set, exec/ изхвърлете" и цикъл "повторен опит от начало", за да отчетете сценария за прекратяване. Мога да се опитам да го напиша като Lua за вас, ако искате - трябва да е около 4 реда.

Ето реализацията на 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);
}

Забележка:ако трябва да параметризирате максимума, ще използвате:

if result > tonumber(ARGV[1]) then

и:

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

(така ARGV[1] взема стойността от max )

Необходимо е да се разбере, че eval /evalsha (което е и ScriptEvaluate повиквания) не се конкурират с други сървърни заявки , така че нищо не се променя между incr и възможния set . Това означава, че не се нуждаем от сложен watch и др. логика.

Ето същото (мисля!) чрез 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;
}

Сложно, а? Прост случай на успех ето тогава:

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*

което е... доста работа и включва спиране на тръбопровода на мултиплексора. По-сложните случаи (неуспешни твърдения, неуспехи на гледане, обвивки) биха имали малко по-различен резултат, но би трябвало да работят.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как да създадете модел в DRY модел с помощта на flow.js (за асинхронни повиквания) в node.js?

  2. Инсталиране на Redis на Debian 9

  3. Не може да се обвърже TCP слушател *:6379 с помощта на Redis в Windows

  4. Вземете Redis ключове и стойности в командния ред

  5. Redis sub/pub и php/nodejs