StackExchange.Redis - LockTake / LockRelease Usage

前端 未结 2 963
再見小時候
再見小時候 2020-12-04 15:59

I am using Redis with StackExchange.Redis. I have multiple threads that will at some point access and edit the value of the same key, so I need to synchronize the manipulati

2条回答
  •  不思量自难忘°
    2020-12-04 16:15

    There is my part of code for lock->get->modify(if required)->unlock actions with comments.

        public static T GetCachedAndModifyWithLock(string key, Func retrieveDataFunc, TimeSpan timeExpiration, Func 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(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;
        }
    

    and usage :

    public class User
    {
        public int ViewCount { get; set; }
    }
    
    var cachedAndModifiedItem = GetCachedAndModifyWithLock( "MyAwesomeKey", () =>
            {
                //return from db or kind of that
                return new User() { ViewCount = 0 };
            }, TimeSpan.FromMinutes(10), user=>
            {
                if (user.ViewCount< 3)
                {
                    user.ViewCount++;
                    return true; //save it to cache
                }
                return false; //do not update it in cache
            }, TimeSpan.FromSeconds(10),true);
    

    That code can be improved (for example, you can add transactions for less count call to cache and etc), but i glad it will be helpfull for you.

提交回复
热议问题