术→技巧, 研发

Redis学习之缓存策略

钱魏Way · · 76 次浏览

Redis 作为缓存使用时,支持多种缓存策略,主要用于在内存不足时淘汰数据。

以下是 Redis 的主要缓存策略及其应用场景和配置方法:

Redis 内置数据淘汰策略

通过 maxmemory-policy 配置项指定淘汰策略(需设置 maxmemory 限制内存大小)。

被动过期(TTL)​

策略:依赖键的过期时间(TTL),Redis 自动删除过期数据。

配置:

# redis.conf
maxmemory-policy volatile-ttl

特点:

  • 优先淘汰 TTL 最短的键。
  • 适用于明确知道数据生命周期的场景(如临时会话数据)。

最近最少使用(LRU)

策略:淘汰最长时间未被访问的键。

变种:

  • allkeys-lru:所有键参与淘汰。
  • volatile-lru:仅有过期时间的键参与淘汰。

配置:

maxmemory-policy allkeys-lru

特点

  • 适合热点数据分布不均的场景(如新闻首页缓存)。
  • Redis 使用近似 LRU 算法(性能优化,非精确)。

最不经常使用(LFU)​

​策略:淘汰访问频率最低的键。

​变种:

  • allkeys-lfu:所有键参与淘汰。
  • volatile-lfu:仅有过期时间的键参与淘汰。

​配置:

maxmemory-policy allkeys-lfu

特点:

  • 适合长期热点数据场景(如用户高频访问的个人资料)。
  • Redis 记录键的访问频率,通过衰减因子避免历史数据影响。

随机淘汰

​策略:随机选择键淘汰。

​变种:

  • allkeys-random:所有键随机淘汰。
  • volatile-random:仅有过期时间的键随机淘汰。

​配置:

maxmemory-policy volatile-random

特点:

  • 适合数据重要性差异不大的场景。
  • 性能最好,但可能淘汰重要数据。

不淘汰(默认)​

​策略:内存满时拒绝写入新数据。

​配置:

maxmemory-policy noeviction

特点:

  • 确保已有数据不丢失,但可能引发写入错误。
  • 适用于数据不可丢失且需持久化的场景(非纯缓存用途)。

缓存设计策略(应用层)

结合 Redis 淘汰策略,通过代码逻辑优化缓存效果。

缓存穿透

问题:大量请求不存在的数据(绕过缓存,直接访问数据库)。

​解决方案:

  • ​空值缓存:对查询结果为空的键,缓存短时间(如NULL:60,60秒)。
  • ​布隆过滤器:在缓存层前置布隆过滤器,拦截无效请求。
# 示例:缓存空值
async def get_user(user_id: int):
    data = await redis.get(f"user:{user_id}")
    if data == "NULL":
        return None  # 直接返回空,避免重复查询
    if data is None:
        user = await db.query_user(user_id)
        if not user:
            await redis.setex(f"user:{user_id}", 60, "NULL")  # 空值缓存60秒
            return None
        await redis.setex(f"user:{user_id}", 300, user.json())
    return data

缓存雪崩

问题:大量缓存同时过期,导致请求直接冲击数据库。

​解决方案:

  • ​随机过期时间:在基础 TTL 上添加随机值(如expire = 300 + random.randint(0, 60))。
  • ​缓存预热:系统启动时加载高频数据到缓存。
# 示例:为键设置随机过期时间
async def cache_data(key, data):
    expire = 300 + random.randint(0, 60)  # 300~360秒随机过期
    await redis.setex(key, expire, data)

缓存击穿

问题:热点数据过期瞬间,大量并发请求击穿缓存。

​解决方案:

  • ​互斥锁(Mutex Lock)​:仅允许一个线程重建缓存,其他线程等待。
  • ​逻辑过期:缓存不设置 TTL,由代码逻辑判断是否异步更新。
# 示例:互斥锁防止击穿
async def get_hot_data():
    data = await redis.get("hot_data")
    if data is None:
        lock = await redis.set("hot_data_lock", 1, nx=True, ex=10)  # 获取锁
        if lock:
            data = await db.query_hot_data()          # 查询数据库
            await redis.setex("hot_data", 300, data)  # 重建缓存
            await redis.delete("hot_data_lock")       # 释放锁
        else:
            await asyncio.sleep(0.1)                  # 等待重试
            return await get_hot_data()
    return data

缓存更新策略

Cache-Aside(旁路缓存)​:

  • ​读:先读缓存,未命中则查数据库并写入缓存。
  • ​写:直接更新数据库,然后删除缓存(或更新缓存)。
# 示例:更新数据库后删除缓存
async def update_user(user_id: int, name: str):
    await db.update_user(user_id, name)        # 更新数据库
    await redis.delete(f"user:{user_id}")      # 删除缓存

Write-Through(通写)​:

  • ​写:先更新缓存,再由缓存同步更新数据库(需缓存支持)。

Write-Back(写回)​:

  • ​写:先更新缓存,延迟异步批量写入数据库(风险:数据丢失)。

策略选择建议

场景 推荐策略
高频访问的热点数据 allkeys-lfu + 长TTL
短期临时数据(如验证码) volatile-ttl + 精确过期时间
内存敏感且数据重要性均匀 allkeys-random
需要保证数据不丢失 noeviction + 持久化
应对突发流量(如电商大促) 缓存预热 + 随机TTL + 限流

Redis 配置示例

# redis.conf
maxmemory 2GB                      # 限制最大内存
maxmemory-policy allkeys-lfu       # 使用 LFU 淘汰策略
maxmemory-samples 10               # 每次淘汰时检查的键数量(精度与性能平衡)

通过合理选择 Redis 淘汰策略和设计应用层缓存逻辑,可以显著提升系统性能和稳定性。生产环境中建议结合监控工具(如 Redis INFO 命令、Prometheus)观察缓存命中率,动态调整策略。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注