StackExchange.Redis: 一个事务是否多次访问服务器?

StackExchange.Redis: Does a transaction hit the server multiple times?

当我通过 SE.Redis 执行交易 (MULTI/EXEC) 时,它会多次访问服务器吗?例如,

        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 个用于执行?或者 SE.Redis 是否足够聪明,可以在本地内存中缓冲任务并在我们调用 ExecuteAsync 时一次性发送所有内容?

它必须发送多个命令,但它不会为每个命令支付延迟成本;具体来说,当您调用 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 个命令,但在延迟方面:由于需要在最终 EXEC/DISCARD 之前的决策点,您需要支付 2 次往返费用。但是,在许多情况下,HEXIST 的结果可能在我们还没有进行检查之前就已经返回给您的现实进一步掩盖了这一点,特别是如果您有任何非- 普通带宽,例如大 newValue.


但是!作为一般规则:您 可以 使用 Redis MULTI/EXEC 做的任何事情:可以 更快、更可靠且错误更少,而是使用 Lua 脚本。看起来我们实际上要在这里做的是:

for the hash cacheKey, if (and only if) the field oldField exists: remove oldField and set newField to newValue

我们可以在 Lua 中非常简单地 执行此操作,因为 Lua 脚本从头到尾都在服务器上执行,不会因竞争连接而中断。这意味着我们不需要担心像原子性这样的事情,即其他连接改变我们正在做决定的数据。所以:

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两次)。


(关于选择KEYS vs ARGV的注意事项:keysvalues的区别对于路由目的很重要,特别是在诸如 redis-cluster 的分片环境中;分片是基于 key 完成的,这里唯一的 keycacheKey;散列中的字段标识符 不影响分片 ,因此出于路由目的,它们是 ,而不是 keys - 因此,您应该通过 ARGV 传送它们,而不是 KEYS;这不会影响您 redis-server,但会影响 [=40] =] 这个区别非常重要,就好像你弄错了一样:服务器很可能会拒绝你的脚本,认为你正在尝试跨槽操作;redis-cluster 上的多键命令仅在以下情况下受支持所有的键都在同一个 slot 上,通常通过 "hash tags")

实现