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)观察缓存命中率,动态调整策略。