首页 > 解决方案 > 如何使用 StackExchange.Redis 使多个操作原子化

问题描述

你好我有以下问题:

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

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

如何使操作“检查哈希是否存在”、“如果不存在则添加”原子的?阅读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中发出,则在我发出(在调试器中)之前,我预计此事务将失败,因为我放置了. 然而事实并非如此。ExecuteAsyncCondition

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

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

标签: .net-coreredistransactionslockingstackexchange.redis

解决方案


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

坏消息

这不起作用,因为 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"如此。

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

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

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

解决此问题的方法是使用常规密钥 ( 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 脚本,在这种情况下您希望以原子方式完成一些条件操作,例如如果 set 在 set 中有那么多 (count) 元素,如何使用带有 count 的 spop 命令

似乎您正在尝试执行分布式锁。有关您可能需要考虑的其他方面,请参阅使用 Redis 的分布式锁。请参阅缓存驱动程序中的分布式原子锁是什么?关于这个的一个好故事。


推荐阅读