programing

asp.net 에서 캐시를 잠그는 가장 좋은 방법은 무엇입니까?

showcode 2023. 6. 9. 22:11
반응형

asp.net 에서 캐시를 잠그는 가장 좋은 방법은 무엇입니까?

프로세스를 장시간 실행하는 경우와 같은 특정 상황에서는 해당 리소스에 대한 다른 사용자의 후속 요청이 캐시에 도달하는 대신 긴 프로세스를 다시 실행하지 않도록 ASP.NET 캐시를 잠그는 것이 중요하다는 것을 알고 있습니다.

ASP.NET에서 캐시 잠금을 구현하는 가장 좋은 c# 방법은 무엇입니까?

기본 패턴은 다음과 같습니다.

  • 캐시에서 값을 확인하고 사용 가능한 경우 반환합니다.
  • 값이 캐시에 없으면 잠금을 구현합니다.
  • 잠금 장치 안에서 캐시를 다시 확인하십시오. 차단되었을 수 있습니다.
  • 값 조회 수행 및 캐시
  • 잠금 해제

코드에서는 다음과 같습니다.

private static object ThisLock = new object();

public string GetFoo()
{

  // try to pull from cache here

  lock (ThisLock)
  {
    // cache was empty before we got the lock, check again inside the lock

    // cache is still empty, so retreive the value here

    // store the value in the cache here
  }

  // return the cached value here

}

완전성을 위해 전체 예는 다음과 같습니다.

private static object ThisLock = new object();
...
object dataObject = Cache["globalData"];
if( dataObject == null )
{
    lock( ThisLock )
    {
        dataObject = Cache["globalData"];

        if( dataObject == null )
        {
            //Get Data from db
             dataObject = GlobalObj.GetData();
             Cache["globalData"] = dataObject;
        }
    }
}
return dataObject;

전체 캐시 인스턴스를 잠글 필요가 없으며, 삽입할 특정 키만 잠글 수 있습니다.즉, 남성 화장실을 사용하는 동안 여성 화장실에 대한 접근을 차단할 필요가 없습니다 :)

아래 구현에서는 동시 사전을 사용하여 특정 캐시 키를 잠글 수 있습니다.이렇게 하면 두 개의 다른 키에 대해 동시에 GetOrAdd()를 실행할 수 있지만 동일한 키에 대해서는 실행할 수 없습니다.

using System;
using System.Collections.Concurrent;
using System.Web.Caching;

public static class CacheExtensions
{
    private static ConcurrentDictionary<string, object> keyLocks = new ConcurrentDictionary<string, object>();

    /// <summary>
    /// Get or Add the item to the cache using the given key. Lazily executes the value factory only if/when needed
    /// </summary>
    public static T GetOrAdd<T>(this Cache cache, string key, int durationInSeconds, Func<T> factory)
        where T : class
    {
        // Try and get value from the cache
        var value = cache.Get(key);
        if (value == null)
        {
            // If not yet cached, lock the key value and add to cache
            lock (keyLocks.GetOrAdd(key, new object()))
            {
                // Try and get from cache again in case it has been added in the meantime
                value = cache.Get(key);
                if (value == null && (value = factory()) != null)
                {
                    // TODO: Some of these parameters could be added to method signature later if required
                    cache.Insert(
                        key: key,
                        value: value,
                        dependencies: null,
                        absoluteExpiration: DateTime.Now.AddSeconds(durationInSeconds),
                        slidingExpiration: Cache.NoSlidingExpiration,
                        priority: CacheItemPriority.Default,
                        onRemoveCallback: null);
                }

                // Remove temporary key lock
                keyLocks.TryRemove(key, out object locker);
            }
        }

        return value as T;
    }
}

파벨이 말한 것을 따라 하자면, 이것이 그것을 쓰는 가장 안전한 방법이라고 생각합니다.

private T GetOrAddToCache<T>(string cacheKey, GenericObjectParamsDelegate<T> creator, params object[] creatorArgs) where T : class, new()
    {
        T returnValue = HttpContext.Current.Cache[cacheKey] as T;
        if (returnValue == null)
        {
            lock (this)
            {
                returnValue = HttpContext.Current.Cache[cacheKey] as T;
                if (returnValue == null)
                {
                    returnValue = creator(creatorArgs);
                    if (returnValue == null)
                    {
                        throw new Exception("Attempt to cache a null reference");
                    }
                    HttpContext.Current.Cache.Add(
                        cacheKey,
                        returnValue,
                        null,
                        System.Web.Caching.Cache.NoAbsoluteExpiration,
                        System.Web.Caching.Cache.NoSlidingExpiration,
                        CacheItemPriority.Normal,
                        null);
                }
            }
        }

        return returnValue;
    }

Craig Shoemaker는 asp.net 캐싱에서 훌륭한 성과를 거두었습니다. http://polymorphicpodcast.com/shows/webperformance/

나는 다음과 같은 확장 방법을 생각해 냈습니다.

private static readonly object _lock = new object();

public static TResult GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action, int duration = 300) {
    TResult result;
    var data = cache[key]; // Can't cast using as operator as TResult may be an int or bool

    if (data == null) {
        lock (_lock) {
            data = cache[key];

            if (data == null) {
                result = action();

                if (result == null)
                    return result;

                if (duration > 0)
                    cache.Insert(key, result, null, DateTime.UtcNow.AddSeconds(duration), TimeSpan.Zero);
            } else
                result = (TResult)data;
        }
    } else
        result = (TResult)data;

    return result;
}

저는 @John Owen과 @user378380 답변을 모두 사용했습니다.이 솔루션을 사용하면 캐시 내에서도 int 및 bool 값을 저장할 수 있습니다.

오류가 있거나 조금 더 잘 작성할 수 있는지 수정 부탁드립니다.

저는 최근에 Correct State Bag Access Pattern이라고 불리는 패턴을 보았는데, 이것은 이것과 관련된 것처럼 보였습니다.

나사산이 안전하도록 조금 수정했습니다.

http://weblogs.asp.net/craigshoemaker/archive/2008/08/28/asp-net-caching-and-performance.aspx

private static object _listLock = new object();

public List List() {
    string cacheKey = "customers";
    List myList = Cache[cacheKey] as List;
    if(myList == null) {
        lock (_listLock) {
            myList = Cache[cacheKey] as List;
            if (myList == null) {
                myList = DAL.ListCustomers();
                Cache.Insert(cacheKey, mList, null, SiteConfig.CacheDuration, TimeSpan.Zero);
            }
        }
    }
    return myList;
}

CodeGuru의 이 기사에서는 다양한 캐시 잠금 시나리오와 ASP.NET 캐시 잠금에 대한 몇 가지 모범 사례에 대해 설명합니다.

ASP.NET에서 캐시 액세스 동기화 중

저는 그 특정한 문제를 해결하는 도서관을 썼습니다.록스.캐싱

또한 저는 이 문제에 대해 자세히 블로그에 올렸고 왜 이것이 중요한지 설명했습니다.

@user378380의 코드를 좀 더 유연하게 수정하였습니다.Tresult는 이제 다른 유형을 순서대로 수락하는 개체를 반환합니다.또한 유연성을 위해 몇 가지 매개 변수를 추가합니다.모든 아이디어는 @user378380의 것입니다.

 private static readonly object _lock = new object();


//If getOnly is true, only get existing cache value, not updating it. If cache value is null then      set it first as running action method. So could return old value or action result value.
//If getOnly is false, update the old value with action result. If cache value is null then      set it first as running action method. So always return action result value.
//With oldValueReturned boolean we can cast returning object(if it is not null) appropriate type on main code.


 public static object GetOrAdd<TResult>(this Cache cache, string key, Func<TResult> action,
    DateTime absoluteExpireTime, TimeSpan slidingExpireTime, bool getOnly, out bool oldValueReturned)
{
    object result;
    var data = cache[key]; 

    if (data == null)
    {
        lock (_lock)
        {
            data = cache[key];

            if (data == null)
            {
                oldValueReturned = false;
                result = action();

                if (result == null)
                {                       
                    return result;
                }

                cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
            }
            else
            {
                if (getOnly)
                {
                    oldValueReturned = true;
                    result = data;
                }
                else
                {
                    oldValueReturned = false;
                    result = action();
                    if (result == null)
                    {                            
                        return result;
                    }

                    cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
                }
            }
        }
    }
    else
    {
        if(getOnly)
        {
            oldValueReturned = true;
            result = data;
        }
        else
        {
            oldValueReturned = false;
            result = action();
            if (result == null)
            {
                return result;
            }

            cache.Insert(key, result, null, absoluteExpireTime, slidingExpireTime);
        }            
    }

    return result;
}

허용된 답변(잠금 밖에서 읽는 것을 권장)은 매우 나쁜 조언이며 2008년부터 시행되고 있습니다.캐시가 동시 사전을 사용하는 경우에는 작동할 수 있지만, 캐시 자체에는 읽기에 대한 잠금이 있습니다.

잠금 상태를 벗어나면 읽기 도중 다른 스레드가 캐시를 수정할 수 있습니다.즉, 읽기가 일관되지 않을 수 있습니다.

예를 들어, 캐시의 구현(아마도 내부가 알려지지 않은 사전)에 따라 항목을 확인하고 캐시의 기본 배열에 있는 특정 인덱스에서 찾을 수 있으며, 그러면 다른 스레드가 캐시를 수정하여 기본 배열의 항목이 더 이상 동일한 순서가 되지 않도록 할 수 있습니다.다른 인덱스/주소에서 캐시의 실제 읽기를 수행할 수 있습니다.

또 다른 시나리오는 항목이 제거되었기 때문에 읽기가 기본 배열 외부에 있는 인덱스일 수 있으므로 예외를 얻을 수 있습니다.

언급URL : https://stackoverflow.com/questions/39112/what-is-the-best-way-to-lock-cache-in-asp-net

반응형