Redis 缓存穿透,击穿,雪崩
概念
- 缓存穿透: 当访问一个并不存在的Key的时候。这个请求就会请求到数据源。在高并发的情况下,可能会压垮数据源 。
- 缓存击穿: Redis中数据的Key存在但是马上要过期。当这个key过期的时候,此时有大量的请求发送过来,这些请求发现缓存过期一般都会从后端数据库加载数据然后再写入缓存,这个时候在高并发的情况下可能会瞬间就把服务端DB击穿。
- 缓存雪崩: 但缓存服务器重启或者大量的缓存集中在某一个时间段失效,这样在失效的时候也会给后端(例如DB带来很大的压力)
解决办法
-
缓存穿透
(1)第一种办法就是做一个足够大的索引map。将所有可能存在的Key都写入map。这样子的话当大量并发的时候请求就会被map所拦截下来而不会去访问数据源。
(2)第二种办法,也是最暴力的解决办法,就是当访问一个并不存在的key的时候,直接把这个空的结果进行缓存,然后为这个空缓存设置一个较短的过期时间。
-
缓存击穿
(1)使用互斥锁 mutex key . 当缓存失效的时候,不要立即去load db,可以先使用带有返回值操作的setnx(具有原子性) 方法来设置一个mutex key 。然后再进行load db操作并重新设置缓存,新的缓存设置成功之后删掉mutex key。这样的话若是或得到了mutex key 说明缓存正在重写。可以使用sleep方法等待一段时间,然后再次调用自身 直到获取不到mutex key为止。
代码实现:
def get_catch_key(key): catch_obj = redis.get(key) if not catch_obj: # 代表设置成功 加上了互斥锁 ,此时进行数据缓存 if(redis.setnx('multx_key',1,3*60) == 1): try: catch_obj = get_value(key) # 此方法从数据库中加载 redis.set(key,catch_obj,expire_secs) # 重新设置缓存 redis.del('multx_key') # 去掉互斥锁 except Exception as e: # 此时的异常捕捉是为了防止数据没有设置成功而导致锁丢失从而阻塞其他线程 redis.del('multx_key') else: # 说明互斥锁设置失败。此时已经有其他的线程在进行数据缓存了。 time.sleep(0.1) # 睡眠0.1s后重试 get_catch_key(key) return catch_obj
-
缓存雪崩
(1)在设置key的过期时间的时候可以使用随机。可以使key在原来的基础上再随机添加 1~5 min的过期时间,这样就不会导致大量的ke在同一时间失效从而对服务器造成大量的冲击。
(2)标记缓存的方法。每次设置一个Key的时候都设置一个标记sign key。无论何时sign key的过期时间都是key的一半。在每次获取数据的时候先获取 sign key若sign key已过期,再加互斥锁mutex key重新更新key 和 sign key的缓存数据以及时间。这样就保证了标记sign key 永远比key先失效,然后再去更新 key。
代码实现:
def get_catch_key(key): catch_obj = redis.get(key) if not catch_obj: if redis.setnx('multx_key', 1, 3 * 60) == 1: catch_obj = get_value(key) redis.set(key, catch_obj, expire_secs) # 重新设置缓存 # 设置标记key缓存过期时间为key的一半 redis.set(key + '_sign', 1, expire_secs / 2) redis.del('multx_key') except Exception as e: redis.del('multx_key') else: time.sleep(0.1) get_catch_key(key) # 这里可以放在Celery异步任务中去处理 提升程序运行效率 if not redis.get(key + '_sign'): # 说明标记key已过期,重新设置缓存和标记key if redis.setnx('multx_sign_key', 1, 3 * 60) == 1: try: catch_obj = get_value(key) redis.set(key, catch_obj, expire_secs) redis.set(key + '_sign', 1, expire_secs / 2) redis.del('multx_sign_key') except Exception as e: redis.del('multx_sign_key') return catch_obj