缓存穿透,缓存击穿,缓存雪崩
一. 前言
NoSQL技术: redis就是NoSQL的一种, 它是一种基于内存的数据库. 为了解决从硬盘读取数据到内存再到数据交互的缓慢过程, 那么使用一种基于内存的数据库redis就是不可或缺的了的, 但是它同时也会引发三个至命的问题那就是: 缓存穿透, 缓存击穿, 缓存雪崩.
二. 缓存穿透
# 问题:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
# 解决:
1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
解决缓存穿透伪代码示例:
from redis import Redis
from django.contrib.auth.models import User
redis_obj = Redis(decode_responses=True)
def cache_shorter(user_id, user_data=None):
"""缓存短点"""
# 都没有查到. 那么就在缓存中设置短点有效时间
redis_obj.set(user_id, user_data, ex=30) # user_data=None
def interface_checking(user_id: str):
"""接口校验"""
if user_id.isdigit():
user_id = int(user_id)
if user_id > 0:
return user_id
return False
def query_data(user_id):
"""查询数据"""
# 解决方式一: 接口校验
user_id = interface_checking(user_id)
if not user_id:
return 'error'
# 查缓存
user_data = redis_obj.get(user_id)
if user_data:
return user_data
# 查数据库
user_data = User.objects.filter(id=user_id).first()
if user_data:
redis_obj.set(user_id, user_data, ex=60 * 5)
return user_data
# 解决方式二: 缓存短点
cache_shorter(user_id, user_data)
return 'error'
res = query_data('缓存和数据库中都没有的数据')
print(res)
三. 缓存击穿
# 问题:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
# 解决:
1. 设置热点数据永远不过期。
2. 加互斥锁
解决缓存击穿伪代码示例:
from redis import Redis
from threading import Lock
from django.contrib.auth.models import User
redis_obj = Redis(decode_responses=True)
mutex = Lock()
def interface_checking(user_id: str):
"""接口校验"""
if user_id.isdigit():
user_id = int(user_id)
if user_id > 0:
return user_id
return False
def cache_shorter(user_id, user_data=None):
"""缓存短点"""
# 都没有查到. 那么就在缓存中设置短点有效时间
redis_obj.set(user_id, user_data, ex=30) # user_data=None
def query_data(user_id):
"""查询数据"""
# 缓存穿透解决方式一: 接口校验
user_id = interface_checking(user_id)
if not user_id:
return 'error'
# 查缓存
user_data = redis_obj.get(user_id)
if user_data:
return user_data
# 缓存击穿解决方式一: 互斥锁
with mutex:
# 查数据库
user_data = User.objects.filter(id=user_id).first()
if user_data:
# 缓存击穿解决方式二: 设置热点数据永远不过期
if user_data == '热点数据':
redis_obj.set(user_id, user_data)
else:
redis_obj.set(user_id, user_data, ex=60 * 5)
return user_data
# 缓存穿透解决方式二: 缓存短点
cache_shorter(user_id, user_data)
return 'error'
四. 缓存雪崩
缓存正常从Redis中获取,示意图如下:
缓存失效瞬间示意图如下:
# 问题:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
# 解决:
1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3. 设置热点数据永远不过期。
加锁处理解决缓存雪崩:
import random
from redis import Redis
from threading import Lock
from django.contrib.auth.models import User
redis_obj = Redis(decode_responses=True)
mutex = Lock()
def interface_checking(user_id: str):
"""接口校验"""
if user_id.isdigit():
user_id = int(user_id)
if user_id > 0:
return user_id
return False
def cache_shorter(user_id, user_data=None):
"""缓存短点"""
# 都没有查到. 那么就在缓存中设置短点有效时间
redis_obj.set(user_id, user_data, ex=30) # user_data=None
def query_data(user_id):
"""查询数据"""
# 缓存穿透解决方式一: 接口校验
user_id = interface_checking(user_id)
if not user_id:
return 'error'
# 查缓存
user_data = redis_obj.get(user_id)
if user_data:
return user_data
# 缓存击穿解决方式一: 互斥锁
with mutex:
# 查数据库
user_data = User.objects.filter(id=user_id).first()
if user_data:
# 缓存击穿解决方式二: 设置热点数据永远不过期
if user_data == '热点数据':
redis_obj.set(user_id, user_data)
else:
# 缓存雪崩解决方式: 缓存数据的过期时间设置随机
random_time = random.uniform(60, 60 * 3)
redis_obj.set(user_id, user_data, ex=60 * 5 + random_time)
return user_data
# 缓存穿透解决方式二: 缓存短点
cache_shorter(user_id, user_data)
return 'error'
文章参考来源: https://www.cnblogs.com/sbj-dawn/p/11116673.html
图参考来源: https://blog.csdn.net/xlgen157387/article/details/79530877