Redis 缓存问题详解及解决方案

一、缓存击穿 (Cache Breakdown)

原理

某个热点 Key 突然过期,同时大量并发请求该 Key,导致请求直接穿透缓存击穿到数据库。

解决方案

  1. 互斥锁 (Mutex Lock)
    当缓存失效时,仅允许一个线程重建缓存,其他线程等待。
  2. 逻辑过期
    不设置物理过期时间,在 Value 中存储过期时间戳,异步更新缓存。

Python 实现 (互斥锁方案)

python 复制代码
import redis
import time
import threading

r = redis.Redis()

def get_data(key):
    # 1. 尝试从缓存读取
    data = r.get(key)
    if data:
        return data
    
    # 2. 尝试获取锁 (SETNX + EXPIRE)
    lock_key = f"lock:{key}"
    if r.set(lock_key, "locked", nx=True, ex=5):  # 设置5秒锁过期
        try:
            # 3. 模拟数据库查询
            data_from_db = "Database Result"  # 真实场景需查DB
            # 4. 写入缓存
            r.setex(key, 60, data_from_db)  # 缓存60秒
            return data_from_db
        finally:
            r.delete(lock_key)
    else:
        # 5. 未获锁则短暂等待重试
        time.sleep(0.1)
        return get_data(key)  # 递归重试

# 测试并发
def test_breakdown():
    key = "hot_key"
    threads = []
    for i in range(10):  # 模拟10个并发请求
        t = threading.Thread(target=lambda: print(get_data(key)))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

test_breakdown()

二、缓存穿透 (Cache Penetration)

原理

请求访问数据库中不存在的数据(如非法ID),导致每次请求都穿透到数据库。

解决方案

  1. 布隆过滤器 (Bloom Filter)
    快速判断 Key 是否可能存在数据库中。
  2. 缓存空对象
    对不存在的数据也进行缓存(设置短过期时间)。

Python 实现 (缓存空对象)

python 复制代码
def get_data_penetration(key):
    # 1. 尝试从缓存读取
    data = r.get(key)
    if data is not None:
        return None if data == b'NULL' else data  # 处理空值
    
    # 2. 查询数据库 (模拟不存在)
    data_from_db = None  # 假设数据库返回空
    
    if data_from_db is None:
        # 3. 缓存空对象 (5分钟过期)
        r.setex(key, 300, 'NULL')  # 特殊值标记
        return None
    else:
        r.setex(key, 60, data_from_db)
        return data_from_db

三、缓存雪崩 (Cache Avalanche)

原理
大量 Key 同时过期,导致所有请求穿透到数据库,引发连锁故障。

解决方案

  1. 随机过期时间
    基础过期时间 + 随机值,分散 Key 过期时间。
  2. 热点数据永不过期
    后台异步更新缓存。
  3. 多级缓存架构
    本地缓存 + Redis 缓存。

Python 实现 (随机过期时间)

python 复制代码
import random

def set_data(key, value):
    # 基础过期时间 + 随机偏移 (0-300秒)
    expire = 3600 + random.randint(0, 300)
    r.setex(key, expire, value)

# 批量设置缓存时使用
set_data("key1", "value1")
set_data("key2", "value2")

四、缓存预热 (Cache Warm-up)

原理

系统上线前预先加载热点数据到缓存,避免冷启动时大量请求穿透到数据库。

实现方式

  1. 手动触发脚本加载数据
  2. 定时任务预热
  3. 启动时自动加载

Python 实现

python 复制代码
def cache_warm_up():
    hot_keys = ["news:1", "product:100", "top:list"]  # 预定义热点Key
    
    for key in hot_keys:
        if not r.exists(key):
            # 从数据库加载数据
            data = f"Data for {key}"  # 真实场景需查DB
            r.setex(key, 3600, data)  # 缓存1小时
            print(f"Preloaded: {key}")

# 系统启动时执行
cache_warm_up()

总结对比表

问题 核心原因 解决方案 关键实现技术
缓存击穿 热点 Key 突然失效 互斥锁、逻辑过期 SETNX + EXPIRE
缓存穿透 查询不存在的数据 布隆过滤器、缓存空对象 特殊值标记 (如'NULL')
缓存雪崩 大量 Key 同时过期 随机过期时间、多级缓存 基础过期时间 + 随机值
缓存预热 冷启动时无缓存 启动时加载热点数据 初始化脚本加载

最佳实践:生产环境中建议组合使用多种方案(如布隆过滤器+空对象+互斥锁+随机过期时间),并配合监控系统实时检测缓存命中率。

相关推荐
YOU OU7 小时前
Redis基础常识与命令
数据库·redis·缓存
吳所畏惧11 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
赵丙双12 小时前
Redis 概率相关的数据类型
redis
Y38153266217 小时前
SERP API + Redis 缓存层:4 种方案对比与选型
数据库·redis·缓存
宇宙第一小趴菜18 小时前
五、Oracle vs MySQL 架构深度对比笔记
mysql·oracle·架构
海兰19 小时前
【高速缓存】RedisVL 高级查询(全文搜索、混合搜索和 多向量搜索)
数据库·人工智能·redis·缓存
记录者t21 小时前
redis常见问题:大key
数据库·redis
青石路21 小时前
如果写Redis序列化与读Redis序列化不一致,你觉得会发生什么
java·redis
難釋懷1 天前
Nginx代理https请求
redis·nginx·https