StackExchange.Redis:事务会多次访问服务器吗?

4
当我通过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时一次性发送所有任务?

1个回答

4

它必须发送多个命令,但每个命令不会承担延迟成本;具体来说,在调用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

thenHEXIST获取结果需要支付延迟成本,因为只有在知道结果后才能决定是否继续事务(发出EXEC并检查结果 - 如果WATCH检测到冲突则结果可能为负),或者是否放弃所有操作(DISCARD)。

所以; 无论如何都要发出6个命令,但就延迟而言:由于需要在最终的EXEC/DISCARD之前做决策,因此需要支付2次往返的延迟成本。但在许多情况下,这本身又可以进一步通过实际情况掩盖,因为HEXIST的结果可能已经在我们检查之前返回给您了,特别是如果您具有任何非平凡带宽,例如大的newValue


然而!作为一般规则:使用 redis 的 MULTI/EXEC 可以用 Lua 脚本代替更快、更可靠、更少出错。看起来我们实际想要做的是:

对于哈希cacheKey,如果(且仅当)字段oldField存在:则删除oldField并将newField设置为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 });

这里的verbatim字符串文字是我们的Lua脚本,注意我们不需要再做一个HEXISTS/HDEL - 我们可以根据HDEL的结果来做决定。在幕后,库会根据需要执行SCRIPT LOAD操作,所以:如果您要多次执行此操作,则无需将脚本本身多次发送到网络。

从客户端的角度来看:您现在只需支付一次延迟费用,并且我们不会重复发送相同的内容(原始代码四次发送了cacheKey,两次发送了oldKey)。
关于选择KEYS vs ARGV的说明:之间的区别对于路由目的非常重要,特别是在redis-cluster等分片环境中;根据key进行分片,而这里唯一的key是cacheKey;哈希中的字段标识符不影响分片,因此对于路由目的,它们是values而不是keys - 因此,您应该通过ARGV而不是KEYS传递它们;这在redis-server上不会影响您,但在redis-cluster上,这种差异非常重要,因为如果您弄错了:服务器很可能会拒绝您的脚本,认为您正在尝试进行跨插槽操作;在redis-cluster上,仅当所有键都在同一slot上时才支持多键命令,通常通过“哈希标记”实现。

非常感谢。我有一个小疑惑。你说“由于需要在最终的EXEC / DISCARD之前进行决策点,因此您正在支付2次往返路线”,并且后来还说“我们不会重复发送相同的内容(原始代码发送了cacheKey四次,而oldKey发送了两次)。 ”我不理解第二句话。在我的原始代码中,当执行完成时,我们难道不会因为“HEXIST”和“EXEC / DISCARD”而产生两次发送的惩罚吗? - nawfal
顺便说一句,如果只调用 ExecuteAsync 并将整个内容转换为 LUA 并在后台运行 ScriptEvaluateAsync,那就太酷了 :P - nawfal
@nawfal,这很可能完全可行,但是需要大量的工作,不是一件简单的事情 :) - Marc Gravell
@nawfal 我们不会在 EXEC/DISCARD 中发送任何数据 - 我只是在这里谈论键和值。 - Marc Gravell
马克,好的,我明白了。 - nawfal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接