StackExchange.Redis: транзакция попадает на сервер несколько раз? - PullRequest
3 голосов
/ 16 июня 2020

Когда я выполняю транзакцию (MULTI / EXE C) через SE.Redis, попадает ли она на сервер несколько раз? Например,

        ITransaction tran = Database.CreateTransaction();
        tran.AddCondition(Condition.HashExists(cacheKey, oldKey));

        HashEntry hashEntry = GetHashEntry(newKeyValuePair);

        Task fieldDeleteTask = tran.HashDeleteAsync(cacheKey, oldKey);
        Task hashSetTask = tran.HashSetAsync(cacheKey, new[] { hashEntry });

        if (await tran.ExecuteAsync())
        {
            await fieldDeleteTask;
            await hashSetTask;
        }

Здесь я выполняю две задачи в транзакции. Означает ли это, что я попал на сервер 4 раза? 1 для MULTI, 1 для удаления, 1 для набора, 1 для exe c? Или SE.Red достаточно умен, чтобы буферизовать задачи в локальной памяти и отправлять все за один раз, когда мы вызываем ExecuteAsync?

1 Ответ

3 голосов
/ 16 июня 2020

Он должен отправить несколько команд , но он не оплачивает задержку за команду; в частности, когда вы вызываете Execute[Async] (и не раньше), он выдает конвейер (все вместе, не дожидаясь ответов):

WATCH cacheKey                  // observes any competing changes to cacheKey
HEXIST cacheKey oldKey          // see if the existing field exists
MULTI                           // starts the transacted commands
HDEL cacheKey oldKey            // delete the existing field
HSET cachKey newField newValue  // assign the new field

затем он оплачивает затраты на задержку, чтобы получить результат из HEXIST, потому что только когда он известен, он может решить, продолжать ли транзакцию (выдавая EXEC и проверяя результат - который может быть отрицательным, если WATCH обнаруживает конфликт), или выбросить все (DISCARD).

Итак; в любом случае будет выдано 6 команд, но с точки зрения задержки: вы платите за 2 передачи в оба конца из-за необходимости принятия решения перед окончательным EXEC / DISCARD. Однако во многих случаях это само по себе может быть дополнительно замаскировано тем фактом, что результат HEXIST может уже возвращаться к вам еще до того, как мы дойдем до проверки, особенно если у вас есть какие-либо нетривиальные пропускная способность, например большая newValue.


Однако! Как правило: все, что вы можете делать с redis MULTI / EXEC: можно сделать быстрее, надежнее и с меньшим количеством ошибок , используя Lua сценарий вместо этого. Похоже, что мы на самом деле пытаемся сделать вот что:

для ha sh cacheKey, если (и только если) поле oldField существует: remove oldField и установите newField на newValue

Мы можем сделать это очень просто в Lua, потому что сценарии Lua выполняются на сервере от начала до конца sh без прерывания из-за конкурирующих соединений. Это означает, что нам не нужно беспокоиться о таких вещах, как атомарность, то есть о других соединениях, изменяющих данные, с которыми мы принимаем решения. Итак:

var success = (bool)await db.ScriptEvaluateAsync(@"
if redis.call('hdel', KEYS[1], ARGV[1]) == 1 then
    redis.call('hset', KEYS[1], ARGV[2], ARGV[3])
    return true
else
    return false
end
", new RedisKey[] { cacheKey }, new RedisValue[] { oldField, newField, newValue });

Дословный строковый литерал здесь - это наш Lua скрипт, с учетом того, что нам больше не нужно делать отдельный HEXISTS / HDEL - мы можем принять наше решение на основе по результату HDEL. За кулисами библиотека выполняет SCRIPT LOAD операций по мере необходимости, поэтому: если вы делаете это много раз, ей не нужно отправлять сам сценарий по сети более одного раза.

точка зрения клиента: теперь вы платите только одну комиссию за задержку, и мы не отправляем одни и те же вещи повторно (исходный код отправлялся cacheKey четыре раза и oldKey дважды) *. 1054 *


(примечание о выборе KEYS против ARGV: различие между ключами и значениями важно для целей маршрутизации, в частности сегментированные среды, такие как redis-cluster; сегментирование выполняется на основе ключа , а единственный ключ здесь cacheKey; идентификаторы полей в хэшах не влияют на сегментирование , поэтому для маршрутизации они являются значениями , а не ключами - и поэтому вы должны передавать их через ARGV, а не KEYS; это выиграет ' t влияет на вас на redis-server, но на redis-cluster эта разница очень заметна. ortant, как будто вы ошиблись: сервер, скорее всего, отклонит ваш сценарий, думая, что вы пытаетесь выполнить операцию с перекрестным слотом; Многоклавишные команды на redis-cluster поддерживаются только тогда, когда все клавиши находятся в одном слоте , обычно достигается с помощью «ha sh тегов»)

...