복붙노트

[REDIS] StackExchange.Redis - LockTake / LockRelease 사용

REDIS

StackExchange.Redis - LockTake / LockRelease 사용

나는 StackExchange.Redis와 레디 스를 사용하고 있습니다. 나는 데이터의 조작을 동기화해야합니다 어느 시점 액세스 및 편집 같은 키의 가치, 그래서 다중 스레드를 가지고있다.

사용 가능한 기능을 보면, 나는 두 가지 기능 TakeLock 및 ReleaseLock가있는 것을 알 수있다. 그러나 이러한 기능 키와 예상되는 하나의 키가 잠겨되는 것이 아니라 값 매개 변수를 모두 가져 가라. GitHub의에 intellisene 문서와 소스는 키와 값 매개 변수에 전달하는 LockTake 및 LockRelease 기능 또는 무엇을 사용하는 방법에 대해 설명하지 않습니다.

Q : StackExchange.Redis에 LockTake 및 LockRelease의 올바른 사용법은 무엇입니까?

내가하는 것을 목표로하고있는 무슨의 의사 코드 예제 :

//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
        //...
    });

해결법

  1. ==============================

    1.잠금 3 개 부분이 있습니다 :

    잠금 3 개 부분이 있습니다 :

    다른 값이 떠오르는하지 않는 경우, GUID는 적절한 "값"을 만들 수 있습니다. 우리는 시스템 이름 (또는 여러 프로세스가 동일한 시스템에서 경쟁 할 수 있다면 컴퓨터 이름의 munged 버전)를 사용하는 경향이있다.

    또한, 잠금을 복용 투기 차단되지 않습니다. 당신이 잠금을 얻기 위해 실패, 따라서이 테스트 할 필요가 있습니다 아마도 일부 재시도 논리를 추가하는 것이 전적으로 가능하다.

    전형적인 예는 수 있습니다 :

    RedisValue token = Environment.MachineName;
    if(db.LockTake(key, token, duration)) {
        try {
            // you have the lock do work
        } finally {
            db.LockRelease(key, token);
        }
    }
    

    다시 (이 시간 초과 경우) 성공을 확인하기 위해 기억 - 작업이 (특히, 루프) 긴 경우, 당신은 중간에 일부 가끔 LockExtend 호출을 추가 할 수 있습니다.

    당신은 경쟁이 신중한 작업에 대해 걱정할 필요가 없습니다 모든 개별 레디 스 명령은, 원자 있음을 유의하십시오. 더 다중 작업 단위 복합체를 들어, 거래 및 스크립팅 옵션이 있습니다.

  2. ==============================

    2.에 대한 코드의 내 부분이 있습니다 자물쇠 -> GET-> 수정 (필요한 경우) -> 주석 해제 작업.

    에 대한 코드의 내 부분이 있습니다 자물쇠 -> GET-> 수정 (필요한 경우) -> 주석 해제 작업.

        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;
        }
    

    및 사용 :

    public class User
    {
        public int ViewCount { get; set; }
    }
    
    var cachedAndModifiedItem = GetCachedAndModifyWithLock<User>( "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);
    

    그 코드는 (예를 들어, 캐시 등 덜 카운트 통화 거래를 추가 할 수 있습니다)하지만 난 기꺼이 당신을 위해 도움이 될 것입니다 향상시킬 수있다.

  3. from https://stackoverflow.com/questions/25127172/stackexchange-redis-locktake-lockrelease-usage by cc-by-sa and MIT license