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

目录

背景

解决方案

总结说明


 

背景

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

解决方案

    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:\Source\BQoolCommon\BQoolCommon.Helpers\Cache\MemoryCacheLayer.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:\Source\BQoolCommon\BQoolCommon.Helpers\Cache\MemoryCacheLayer.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();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以看出synchronized并不适合解决缓存击穿问题,因为synchronized是Java中的一种锁机制,而Redis是一种内存数据库,两者并不直接相关。在Java中,可以使用synchronized关键字来实现锁机制,但是这种锁机制只适用于单机环境,无法解决分布式环境下的缓存击穿问题。 如果要解决分布式环境下的缓存击穿问题,可以使用分布式锁来实现。常见的分布式锁有Redis分布式锁和ZooKeeper分布式锁。其中,Redis分布式锁是通过Redis实现的,而ZooKeeper分布式锁是通过ZooKeeper实现的。这两种分布式锁都可以用来解决缓存击穿问题。 下面是使用Redis分布式锁来解决缓存击穿问题的示例代码: ```python import redis import time # 连接Redis r = redis.Redis(host='localhost', port=6379, db=0) # 获取缓存数据的函数 def get_data(key): # 先尝试从缓存中获取数据 data = r.get(key) if data: return data # 如果缓存中没有数据,则加锁 lock_key = key + '_lock' lock_value = int(time.time() * 1000) if r.setnx(lock_key, lock_value): # 如果加锁成功,则从数据库中获取数据,并将数据写入缓存 data = get_data_from_db(key) r.set(key, data) # 释放锁 r.delete(lock_key) return data else: # 如果加锁失败,则等待一段时间后重试 time.sleep(0.1) return get_data(key) # 从数据库中获取数据的函数 def get_data_from_db(key): # TODO: 从数据库中获取数据 pass # 测试代码 data = get_data('my_key') print(data) ``` 在上面的代码中,我们使用了Redis的setnx命令来实现分布式锁。setnx命令可以将一个键值对写入Redis,但是只有在该键不存在时才会写入成功。因此,我们可以将缓存的键加上一个后缀'_lock',然后使用setnx命令来尝试加锁。如果加锁成功,则从数据库中获取数据,并将数据写入缓存;如果加锁失败,则等待一段时间后重试。在获取数据完成后,我们需要使用delete命令来释放锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值