Redis缓存穿透

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值