什么是 Redis 缓存穿透
Redis 缓存穿透是指发起的请求中缓存和数据库中均没有缓存的数据,每次请求都会直达数据库,导致数据库压力加大,最终可能会导致系统崩溃的情况。
缓存穿透通常是由于黑客攻击或用户恶意攻击造成的。黑客通常会发起一些畸形请求,占用大量的资源,而用户则可能不是故意的,例如在短时间内发送大量相同的请求等。
Redis 缓存穿透的解决方法
布隆过滤器
布隆过滤器是一种内存效率高、错误率极低的概率型数据结构。它可以帮助我们快速判断一个元素是否在集合中存在,或者一个字符串是否是某个集合中的成员。
在处理 Redis 缓存穿透的时候,可以利用布隆过滤器快速过滤掉不存在的数据请求,从而避免了直接访问数据库造成的开销。
下面是使用 Redis 布隆过滤器的示例代码:
// www.javascriptcn.com code example
import redis
import hashlib
from pybloom import BloomFilter
# 连接 Redis 数据库
client = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 初始化一个 Bloom 过滤器
bf = BloomFilter(capacity=1000000, error_rate=0.001)
# 从 Redis 缓存中获取数据
def get_data(key):
# 对请求数据进行哈希
data_hash = hashlib.sha256(key.encode('utf-8')).hexdigest()
# 先判断数据是否在布隆过滤器中
if data_hash in bf:
# 如果在布隆过滤器中,则查询 Redis 缓存
data = client.get(key)
if data is not None:
# 如果缓存中有数据,则返回
return data
else:
# 如果缓存中没有数据,则返回 None
return None
else:
# 如果不在布隆过滤器中,则直接返回 None
return None
# 将数据添加到 Redis 缓存中
def add_data(key, value):
# 对数据进行哈希,并将哈希值添加到布隆过滤器中
data_hash = hashlib.sha256(key.encode('utf-8')).hexdigest()
bf.add(data_hash)
# 将数据添加到 Redis 中缓存
client.set(key, value)缓存空对象
缓存空对象是一种可以有效避免缓存穿透的解决方案。该方法就是当查询条件没有返回结果时,我们就设置一个默认的空对象,然后将这个空对象缓存到 Redis 中,这样在下一次请求中,当条件相同时,就会直接从 Redis 中获取空对象,而不会再次查询数据库。
示例代码如下:
// www.javascriptcn.com code example
import redis
import json
# 连接 Redis 数据库
client = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 从 Redis 缓存中获取数据
def get_data(key):
# 先查询 Redis 缓存
data = client.get(key)
# 如果缓存中没有数据,则查询数据库
if data is None:
data = db.query(key)
# 如果数据库查询结果为空,则设置默认的空对象并添加到缓存中
if data is None:
data = json.dumps({'data': []})
client.set(key, data, ex=600)
# 如果缓存中有数据,则直接返回
return data缓存击穿
缓存击穿是指某个热点数据在 Redis 缓存中过期了,导致大量请求直接落到数据库上,造成数据库的压力过大,最终导致系统崩溃。
解决缓存击穿可以采用以下两种方法:
加锁
使用 Redis 的 SETNX 操作实现加锁机制,当请求到达时,先尝试获取该键的锁,如果获取成功,则查询 Redis 缓存数据,如果获取失败,则等待一段时间后重试或直接返回空数据。
示例代码如下:
// www.javascriptcn.com code example
import redis
import time
# 连接 Redis 数据库
client = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 加锁获取缓存数据
def get_data(key):
# 加锁
lock = client.setnx(key + '_lock', '1')
if lock is None or lock == 0:
# 获取锁失败,等待一段时间后重试或直接返回空数据
time.sleep(0.1)
return None
# 查询 Redis 缓存数据
data = client.get(key)
# 释放锁
client.delete(key + '_lock')
# 如果缓存中没有数据,则查询数据库
if data is None:
data = db.query(key)
# 如果数据库查询结果不为空,则添加到 Redis 缓存中
if data is not None:
client.set(key, data, ex=600)
# 如果缓存中有数据,则直接返回
return data双重检测锁
使用双重检测锁的方式解决缓存击穿问题,即请求先查找缓存数据,如果缓存中没有数据,则尝试获取锁,再次检查缓存数据,如果缓存中还是没有数据,则查询数据库,并将查询结果添加到 Redis 缓存中。
示例代码如下:
// www.javascriptcn.com code example
import redis
import time
# 连接 Redis 数据库
client = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 双重检测锁获取缓存数据
def get_data(key):
# 先查询 Redis 缓存数据
data = client.get(key)
# 如果缓存中没有数据,则尝试获取锁
if data is None:
lock = client.setnx(key + '_lock', '1')
if lock is None or lock == 0:
# 获取锁失败,等待一段时间后直接返回空数据
time.sleep(0.1)
return None
# 再次查询 Redis 缓存数据
data = client.get(key)
# 如果还是没有数据,则查询数据库,并将查询结果添加到缓存中
if data is None:
data = db.query(key)
if data is not None:
client.set(key, data, ex=600)
# 释放锁
client.delete(key + '_lock')
# 如果缓存中有数据,则直接返回
return data结语
通过以上三种方式,我们可以成功解决 Redis 缓存穿透和缓存击穿的问题,从而保证了系统的稳定性和可靠性。
需要注意的是,在对特定数据使用缓存的时候,我们应该同时考虑缓存有效期,以避免脏数据的出现。同时,应该根据实际业务场景进行选择相应的解决方案,以达到最优的缓存效果。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d68c49a941bf7134c554b7