【问题标题】:How to make multiple operations atomic using StackExchange.Redis如何使用 StackExchange.Redis 使多个操作原子化
【发布时间】:2020-01-29 13:39:42
【问题描述】:

您好,我有以下问题:

我有一个包含字符串的散列。这个散列将被多个用户查询。

当用户自带Key 时,首先检查它是否存在于此哈希中,如果不存在,则添加它。

如何使操作“检查哈希是否存在”、“如果不存在则添加”原子操作? 阅读redis文档似乎Watch是我需要的。基本上开始一个事务,如果变量发生变化就结束它。

我尝试使用Condition.HashNotExists 无济于事:

class Program {

        public static async Task<bool> LockFileForEditAsync(int fileId) {
            var database = ConnectionMultiplexer.Connect(CON).GetDatabase();
            var exists = await database.HashExistsAsync("files", fileId); //this line is for  shorting the transaction if hash exists 

            if (exists) {
                return false;
            }

            var tran = database.CreateTransaction();
            tran.AddCondition(Condition.HashNotExists("files", fileId));
            var setKey = tran.HashSetAsync("files", new HashEntry[] { new HashEntry(fileId, 1) });
            var existsTsc = tran.HashExistsAsync("files", fileId);

            if (!await tran.ExecuteAsync()) {
                return false;
            }

            var rezult = await existsTsc;
            return rezult;
        }

        public const string CON = "127.0.0.1:6379,ssl=False,allowAdmin=True,abortConnect=False,defaultDatabase=0";

        static async Task Main(string[] args) {
            int fid = 1;
            var locked = await LockFileForEditAsync(fid);
        }
    }

如果我通过redis-cli 连接并在cli 中发出:hset files {fileId} 1,对之前我发出ExecuteAsync(在调试器中)我预计此事务将失败,因为我放置了Condition。然而事实并非如此。

我怎样才能基本上使用redis命令在两个操作上放置类似锁的东西:

  1. 检查hashentry是否存在
  2. 添加哈希条目

【问题讨论】:

    标签: .net-core redis transactions locking stackexchange.redis


    【解决方案1】:

    坏消息,好消息,以及其他一切......

    坏消息

    这不起作用,因为 SE.Redis 管道传输事务。这意味着当调用ExecuteAsync() 时,所有事务命令都会同时发送到服务器。但是,首先评估条件。

    tran.AddCondition(Condition.HashNotExists("files", fileId)); 转换为:

    WATCH "files"
    HEXISTS "files" "1"
    

    当调用ExecuteAsync() 来评估条件时,首先发送这两个命令。如果条件成立 (HEXISTS "files" "1" = 0),则发送其余事务命令。

    这有效地确保没有误报,因为如果在两者之间修改了files 键(同时 SE.Redis 正在评估条件),WATCH 将使事务失败。

    问题在于漏报。例如,如果在 SE.Redis 评估条件时设置了哈希的另一个字段,则事务也会失败。 WATCH "files" 做到了。

    我通过在调用ExecuteAsync() 时运行redis-benchmark -c 5 HSET files 2 1 对此进行了测试。条件通过,但交易失败,虽然字段“1”不存在,因为字段“2”设置在两者之间。

    我在单独的 redis-cli 窗口中使用 MONITOR 命令进行了验证。这对于排除未满足的期望或只是查看服务器的实际情况和时间非常方便。

    WATCH 在您关心哈希的某个字段时没有帮助,因为它会在触及其他字段时产生误报。

    对此的解决方案是使用常规密钥 (files:1)。

    好消息

    有一个命令可以做你想做的事:HSETNX

    这完全简化了您的 LockFileForEditAsync():

        public static async Task<bool> LockFileForEditAsync(int fileId)
        {
            var database = ConnectionMultiplexer.Connect(CON).GetDatabase();
            var setKey = await database.HashSetAsync("files", fileId, 1, When.NotExists);
            return setKey;
        }
    

    注意When.NotExists 参数。这会导致发送HSETNX "files" "1" "1" 命令。

    其他一切......

    您可以在这种情况下使用Lua scripts,在这种情况下,您希望以原子方式完成一些条件操作,例如how to use spop command with count if set have that much (count) element in set

    您似乎正在尝试执行分布式锁。有关您可能要考虑的其他方面,请参阅Distributed locks with Redis。请参阅What is distributed Atomic lock in caches drivers? 了解关于此的精彩故事。

    【讨论】:

    • 显然它适用于我的变体。如果不存在,我不想只添加。我想添加并返回 true(如果它不存在并且创建成功),如果它确实存在则返回 false ,或者,它试图创建它,但同时有人创建了它。(交易)
    • 这正是 HSETNX 所做的:“仅当字段尚不存在时,将存储在 key 的哈希中的字段设置为 value。如果 key 不存在,则创建一个包含哈希的新 key。 如果字段已经存在,则此操作无效。"。如果您使用一个命令,您的最后一个条件“它试图创建它但有人在此期间创建它。(事务)”不适用,Redis 是单线程的,因此一个命令是自动原子的。
    • 查看 HSETNX 的返回值:如果字段是散列中的新字段并且设置了值,则为 1。如果字段已经存在于哈希中并且没有执行任何操作,则为 0。我看不出这与您想要的不完全匹配:-)您指的是什么变体?
    • 好吧,在我的代码中,您可以看到每个用户首​​先尝试查看哈希是否存在,然后如果不存在则添加它。在用户 1 检查哈希并成功的情况下,就在写入哈希用户之前2 也检查哈希并成功(用户 1 尚未写入),然后用户 2 事务的条件将失败,因为此时用户 1 已写入。
    • 更新了答案,如果另一个客户端设置字段 1 确实有效,但如果另一个客户端设置另一个字段则失败(假阴性)。感谢分享,迫使我深入挖掘,我学到了更多
    猜你喜欢
    • 2012-10-03
    • 2019-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-28
    相关资源
    最近更新 更多