首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >StackExchange.Redis - LockTake / LockRelease用法

StackExchange.Redis - LockTake / LockRelease用法
EN

Stack Overflow用户
提问于 2014-08-04 20:41:37
回答 2查看 21.1K关注 0票数 37

我和StackExchange.Redis一起使用Redis。我有多个线程,在某个时候将访问和编辑同一键的值,因此我需要同步数据的操作。

看一下可用的函数,我发现有两个函数,TakeLock和ReleaseLock。但是,这些函数同时使用键和值参数,而不是要锁定的预期的单键。GitHub上的智能文档和源代码没有解释如何使用LockTake和LockRelease函数,也没有解释如何传递键和值参数。

问: LockTake和LockRelease在StackExchange.Redis中的正确用法是什么?

我想要做的伪代码例子:

代码语言:javascript
运行
复制
//Add Items Before Parallel Execution
redis.StringSet("myJSONKey", myJSON);

//Parallel Execution
Parallel.For(0, 100, i =>
    {
        //Some work here
        //....

        //Lock
        redis.LockTake("myJSONKey");

        //Manipulate
        var myJSONObject = redis.StringGet("myJSONKey");
        myJSONObject.Total++;
        Console.WriteLine(myJSONObject.Total);
        redis.StringSet("myJSONKey", myNewJSON);

        //Unlock
        redis.LockRelease("myJSONKey");

        //More work here
        //...
    });
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-08-05 11:53:12

一个锁有三个部分:

  • 键(数据库中锁的唯一名称)
  • 值(调用方定义的令牌,既可用于指示谁“拥有”锁,也可用于检查是否正确地释放和扩展锁)。
  • 持续时间(锁定故意是有限持续时间的事情)

如果没有想到其他价值,guid可能会产生一个合适的“值”。我们倾向于使用机器名称(如果多个进程可能在同一台机器上竞争,则使用机器名称的任意版本)。

另外,请注意,使用锁是推测性的,而不是阻塞。您完全有可能无法获得锁,因此您可能需要对此进行测试,并可能添加一些重试逻辑。

一个典型的例子可能是:

代码语言:javascript
运行
复制
RedisValue token = Environment.MachineName;
if(db.LockTake(key, token, duration)) {
    try {
        // you have the lock do work
    } finally {
        db.LockRelease(key, token);
    }
}

请注意,如果工作很长(特别是循环),您可能希望在中间添加一些偶然的LockExtend调用,再次记住检查是否成功(万一超时)。

还请注意,所有单独的redis命令都是原子的,因此您不需要担心两个离散操作的竞争。对于更复杂的多操作单元,事务和脚本都是选项。

票数 63
EN

Stack Overflow用户

发布于 2015-09-29 19:42:55

这是我编写的一部分代码,用于锁->get->修改(如果需要)带有注释的->unlock操作。

代码语言:javascript
运行
复制
    public static T GetCachedAndModifyWithLock<T>(string key, Func<T> retrieveDataFunc, TimeSpan timeExpiration, Func<T, bool> modifyEntityFunc,
       TimeSpan? lockTimeout = null, bool isSlidingExpiration=false) where T : class
    {
        
        int lockCounter = 0;//for logging in case when too many locks per key
        Exception logException = null;

        var cache = Connection.GetDatabase();
        var lockToken = Guid.NewGuid().ToString(); //unique token for current part of code
        var lockName = key + "_lock"; //unique lock name. key-relative.
        T tResult = null;
        
        while ( lockCounter < 20)
        {
            //check for access to cache object, trying to lock it
            if (!cache.LockTake(lockName, lockToken, lockTimeout ?? TimeSpan.FromSeconds(10)))
            {
                lockCounter++;
                Thread.Sleep(100); //sleep for 100 milliseconds for next lock try. you can play with that
                continue;
            }

            try
            {
                RedisValue result = RedisValue.Null;

                if (isSlidingExpiration)
                {
                    //in case of sliding expiration - get object with expiry time
                    var exp = cache.StringGetWithExpiry(key);
                    
                    //check ttl.
                    if (exp.Expiry.HasValue && exp.Expiry.Value.TotalSeconds >= 0)
                    {
                        //get only if not expired
                        result = exp.Value;
                    }
                }
                else //in absolute expiration case simply get
                {
                    result = cache.StringGet(key);
                }

                //"REDIS_NULL" is for cases when our retrieveDataFunc function returning null (we cannot store null in redis, but can store pre-defined string :) )
                if (result.HasValue && result == "REDIS_NULL") return null;
                //in case when cache is epmty
                if (!result.HasValue)
                {
                    //retrieving data from caller function (from db from example)
                    tResult = retrieveDataFunc();

                    if (tResult != null)
                    {
                        //trying to modify that entity. if caller modifyEntityFunc returns true, it means that caller wants to resave modified entity.
                        if (modifyEntityFunc(tResult))
                        {
                            //json serialization
                            var json = JsonConvert.SerializeObject(tResult);
                            cache.StringSet(key, json, timeExpiration);
                        }
                    }
                    else
                    {
                        //save pre-defined string in case if source-value is null.
                        cache.StringSet(key, "REDIS_NULL", timeExpiration);
                    }
                }
                else
                {
                    //retrieve from cache and serialize to required object
                    tResult = JsonConvert.DeserializeObject<T>(result);
                    //trying to modify
                    if (modifyEntityFunc(tResult))
                    {
                        //and save if required
                        var json = JsonConvert.SerializeObject(tResult);
                        cache.StringSet(key, json,  timeExpiration);
                    }
                }

                //refresh exiration in case of sliding expiration flag
                if(isSlidingExpiration)
                    cache.KeyExpire(key, timeExpiration);
            }
            catch (Exception ex)
            {
                logException = ex;
            }
            finally
            {                    
                cache.LockRelease(lockName, lockToken);
            }
            break;
        }

        if (lockCounter >= 20 || logException!=null)
        {
            //log it
        }

        return tResult;
    }

和用法:

代码语言:javascript
运行
复制
public class User
{
    public int ViewCount { get; set; }
}

var cachedAndModifiedItem = GetCachedAndModifyWithLock<User>( 
        "MyAwesomeKey", //your redis key
        () => // callback to get data from source in case if redis's store is empty
        {
            //return from db or kind of that
            return new User() { ViewCount = 0 };
        }, 
        TimeSpan.FromMinutes(10), //object expiration time to pass in Redis
        user=> //modify object callback. return true if you need to save it back to redis
        {
            if (user.ViewCount< 3)
            {
                user.ViewCount++;
                return true; //save it to cache
            }
            return false; //do not update it in cache
        },
        TimeSpan.FromSeconds(10), //lock redis timeout. if you will have race condition situation - it will be locked for 10 seconds and wait "get_from_db"/redis read/modify operations done.
        true //is expiration should be sliding.
        );

该代码可以改进(例如,您可以添加事务以减少对缓存的计数调用等等),但我很高兴它将对您有所帮助。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25127172

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档