Redis缓存穿透是指客户端频繁查询Redis中不存在的键,而这些键也不存在于数据库中。由于缓存中没有这些键,所有请求都会打到后端数据库,可能导致数据库高负载甚至宕机。
以下是解决Redis缓存穿透的几种常用方案及其 Python 实现:
1. 缓存空值
当查询的键在数据库中不存在时,将其结果(如None或空值)写入缓存,设置一个较短的过期时间,防止重复请求打到数据库。
import redis
def get_cache_with_empty_value(key):
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 模拟查询数据库
data = fetch_from_db(key)
if data is None:
# 缓存空值,设置较短过期时间(如60秒)
r.setex(key, 60, "NULL")
return None
else:
# 缓存正常数据
r.setex(key, 3600, data) # 正常数据缓存1小时
return data
elif cache_value == b"NULL":
return None # 如果缓存的是空值,返回None
return cache_value
def fetch_from_db(key):
# 模拟数据库查询,返回None表示数据不存在
database = {"key1": "value1", "key2": "value2"}
return database.get(key)
2. 布隆过滤器
使用布隆过滤器对所有可能的合法键进行快速判断,如果键不在布隆过滤器中,直接返回而不访问数据库或缓存。
from pybloom_live import ScalableBloomFilter
import redis
# 初始化布隆过滤器
bloom = ScalableBloomFilter(initial_capacity=1000, error_rate=0.001)
# 添加可能存在的键
bloom.add("key1")
bloom.add("key2")
def get_cache_with_bloom(key):
if key not in bloom:
return None # 如果布隆过滤器判定键不存在,直接返回
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 查询数据库并缓存
data = fetch_from_db(key)
if data is None:
r.setex(key, 60, "NULL") # 缓存空值
return None
else:
r.setex(key, 3600, data) # 缓存正常数据
return data
elif cache_value == b"NULL":
return None # 缓存空值
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
database = {"key1": "value1", "key2": "value2"}
return database.get(key)
3. 参数校验
对用户请求的键进行严格校验,直接过滤掉明显不合法的键,减少无效查询。例如,可以根据键的格式或规则进行判断。
import re
import redis
def is_valid_key(key):
# 假设键必须是字母或数字,且长度在1到32之间
return bool(re.match(r"^[a-zA-Z0-9]{1,32}$", key))
def get_cache_with_validation(key):
if not is_valid_key(key):
return None # 如果键不合法,直接返回
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 查询数据库并缓存
data = fetch_from_db(key)
if data is None:
r.setex(key, 60, "NULL") # 缓存空值
return None
else:
r.setex(key, 3600, data) # 缓存正常数据
return data
elif cache_value == b"NULL":
return None # 缓存空值
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
database = {"key1": "value1", "key2": "value2"}
return database.get(key)
4. 使用默认值
对不存在的键返回默认值,而不是直接查询数据库。例如,为缺失的键返回一个固定的占位符。
import redis
def get_cache_with_default_value(key):
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 查询数据库并缓存
data = fetch_from_db(key)
if data is None:
r.setex(key, 60, "DEFAULT_VALUE") # 缓存默认值
return "DEFAULT_VALUE"
else:
r.setex(key, 3600, data) # 缓存正常数据
return data
elif cache_value == b"DEFAULT_VALUE":
return "DEFAULT_VALUE" # 返回默认值
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
database = {"key1": "value1", "key2": "value2"}
return database.get(key)
5. 限流保护
对同一个键的请求进行限流,防止大量无效请求直接打到数据库。
import redis
import time
def is_request_allowed(key, limit=10, period=60):
"""简单的限流算法,每分钟允许最多10次请求"""
r = redis.Redis(host='localhost', port=6379, db=0)
current_count = r.incr(key)
if current_count == 1:
r.expire(key, period) # 设置限流周期
return current_count <= limit
def get_cache_with_rate_limit(key):
if not is_request_allowed(f"rate_limit:{key}"):
return None # 超过限流限制时,直接返回
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 查询数据库并缓存
data = fetch_from_db(key)
if data is None:
r.setex(key, 60, "NULL") # 缓存空值
return None
else:
r.setex(key, 3600, data) # 缓存正常数据
return data
elif cache_value == b"NULL":
return None # 缓存空值
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
database = {"key1": "value1", "key2": "value2"}
return database.get(key)
6. 多级缓存
使用本地缓存(如内存缓存)和远程缓存(如Redis)结合,先查询本地缓存,减少请求打到远程缓存和数据库。
local_cache = {}
def get_cache_with_local_and_redis(key):
# 优先查询本地缓存
if key in local_cache:
return local_cache[key]
r = redis.Redis(host='localhost', port=6379, db=0)
cache_value = r.get(key)
if cache_value is None:
# 查询数据库并缓存
data = fetch_from_db(key)
if data is None:
r.setex(key, 60, "NULL") # 缓存空值
return None
else:
r.setex(key, 3600, data) # 缓存正常数据
local_cache[key] = data # 更新本地缓存
return data
elif cache_value == b"NULL":
return None # 缓存空值
# 更新本地缓存
local_cache[key] = cache_value
return cache_value
def fetch_from_db(key):
# 模拟数据库查询
database = {"key1": "value1", "key2": "value2"}
return database.get(key)