巧用lock解决缓存击穿的解决方案

  • A+
所属分类:.NET技术
摘要

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。


背景

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案

    1、设置热点数据永远不过期。

    2、加互斥锁,互斥锁参考代码如下:

         2.1、根据key生成object()

private static object GetMemoryCacheLockObject(string key)         {             string cacheLockKey = string.Format(MemoryCacheLockObjectFormat, key);             lock (CacheObject)             {                 var lockObject = CacheObject[cacheLockKey];                 if (lockObject == null)                 {                     // 取得每個 Key專屬的 lock object;若同時有多個 thread要求相同資料,只會(到資料庫)查第一次,剩下的從 cache讀取                     lockObject = new object();                     CacheObject.Set(                         cacheLockKey,                         lockObject,                         new System.Runtime.Caching.CacheItemPolicy()                         {                             AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(10)                         }                     );                 }                  return lockObject;             }         }

2.2、lock住GetMemoryCacheLockObject(key)

 public T Get<T>(string key, Func<T> getDataWork, TimeSpan absoluteExpireTime, bool forceRefresh = false, bool returnCopy = true) where T : class         {             try             {                 lock (GetMemoryCacheLockObject(key))                 {                     /* System.ArgumentNullException: Value cannot be null. at System.Threading.Monitor.Enter(Object obj) at BQoolCommon.Helpers.Cache.MemoryCacheLayer.Get[T](String key, Func`1 getDataWork, TimeSpan absoluteExpireTime, Boolean forceRefresh, Boolean returnCopy) in D:SourceBQoolCommonBQoolCommon.HelpersCacheMemoryCacheLayer.cs:line 46                      */                     T result = CacheObject[key] as T;                      if (result != null && forceRefresh)                     {// 是否清除Cache,強制重查                         result = null;                     }                      if (result == null)                     {                         //執行取得資料的委派作業                         result = getDataWork();                          if (result != null)                         {                             Set(key, result, absoluteExpireTime);                         }                     }                      if (returnCopy)                     {                         //複製一份新的參考                         string serialize = JsonConvert.SerializeObject(result);                         return JsonConvert.DeserializeObject<T>(serialize);                     }                     else                     {                         return result;                     }                 }             }             catch             {                 return getDataWork();             }         }

总结说明

1、缓存中有数据,直接走下述代码就返回结果了

  T result = CacheObject[key] as T;

  2、缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

 try             {                 lock (GetMemoryCacheLockObject(key))                 {                     /* System.ArgumentNullException: Value cannot be null. at System.Threading.Monitor.Enter(Object obj) at BQoolCommon.Helpers.Cache.MemoryCacheLayer.Get[T](String key, Func`1 getDataWork, TimeSpan absoluteExpireTime, Boolean forceRefresh, Boolean returnCopy) in D:SourceBQoolCommonBQoolCommon.HelpersCacheMemoryCacheLayer.cs:line 46                      */                     T result = CacheObject[key] as T;

3、取得每个 Key专有的 lock object;若同时有多个 thread要求相同资料,只会(到数据库)查第一次,剩下的从 cache读取。

 string cacheLockKey = string.Format(MemoryCacheLockObjectFormat, key);             lock (CacheObject)             {                 var lockObject = CacheObject[cacheLockKey];                 if (lockObject == null)                 {                     // 取得每個 Key專屬的 lock object;若同時有多個 thread要求相同資料,只會(到資料庫)查第一次,剩下的從 cache讀取                     lockObject = new object();