Redis 持久化与过期策略 ⭐⭐⭐
面试题:Redis 的持久化方式有哪些?过期策略是什么?
核心回答
Redis 提供两种持久化方式:RDB(数据快照)和 AOF(命令日志),以及 过期策略(惰性删除+定期删除)和 内存淘汰策略。
一、RDB 持久化
原理
RDB(Redis Database)通过创建数据快照的方式,将内存中的所有数据写入磁盘。
RDB 文件生成过程:
1. Redis fork 一个子进程
2. 子进程遍历内存,生成 RDB 文件
3. 替换旧的 RDB 文件
触发方式
# 手动触发
SAVE # 阻塞 Redis,直到 RDB 文件生成完成
BGSAVE # 后台异步生成 RDB 文件
# 自动触发(配置在 redis.conf)
save 900 1 # 900 秒内至少 1 个 key 变化
save 300 10 # 300 秒内至少 10 个 key 变化
save 60 10000 # 60 秒内至少 10000 个 key 变化
优点
- 恢复速度快:RDB 是紧凑的二进制文件,恢复大量数据时比 AOF 快
- 性能影响小:fork 的子进程完成持久化,不影响主进程
- 适合备份:可以定时备份 RDB 文件
缺点
- 可能丢失数据:最后一次快照之后的数据会丢失
- fork 开销:数据量大时,fork 可能阻塞
二、AOF 持久化
原理
AOF(Append Only File)通过记录每个写命令来保存数据。
AOF 工作流程:
1. 命令执行 → 结果写入 aof_buf
2. 触发刷盘 → aof_buf 写入 AOF 文件
3. 重写 → 压缩 AOF 文件
配置
# 开启 AOF
appendonly yes
# 刷盘策略
appendfsync always # 每次写命令都刷盘(慢,但最安全)
appendfsync everysec # 每秒刷盘一次(默认,推荐)
appendfsync no # 由操作系统决定何时刷盘(快,但可能丢数据)
AOF 重写
# 手动触发
BGREWRITEAOF
# 自动触发
auto-aof-rewrite-percentage 100 # 文件大小达到上次重写时的 100% 时触发
auto-aof-rewrite-min-size 64mb # 文件至少达到 64MB 时才触发
重写原理:
# 重写不是读取旧 AOF 文件
# 而是直接读取当前内存数据,生成写命令
# 例如:
SET name "张三"
SET name "李四"
SET name "王五"
# 重写后只保留:SET name "王五"
三、RDB vs AOF
| 特性 | RDB | AOF |
|---|---|---|
| 数据安全性 | 可能有丢失 | 可配置(每秒/每次) |
| 恢复速度 | 快 | 慢(需重放命令) |
| 文件大小 | 小(紧凑) | 大(包含所有命令) |
| CPU 开销 | fork 开销 | 需要重写 |
| 适用场景 | 备份、恢复 | 数据安全要求高 |
四、Redis 过期策略
设置过期时间
# 设置过期时间
EXPIRE key 60 # 60 秒后过期
EXPIREAT key 1234567890 # 设置过期时间戳
PEXPIRE key 60000 # 毫秒
# 查看剩余时间
TTL key
PTTL key
# 移除过期时间(永不过期)
PERSIST key
三种过期策略
// 1. 定时删除
// 为每个 key 创建定时器,过期立即删除
// 优点:内存友好,及时释放
// 缺点:CPU 开销大,大量定时器影响性能
// 2. 惰性删除
// 访问 key 时才判断是否过期,过期则删除
// 优点:CPU 友好
// 缺点:内存不友好,过期 key 可能堆积
// 3. 定期删除
// 每隔一段时间,扫描一定数量的 key,删除过期的
// 折中方案:平衡 CPU 和内存
Redis 采用的策略
Redis 采用 惰性删除 + 定期删除 的组合策略:
// 惰性删除:在 get 时检查
def get(key):
if key in db:
if key.is_expired():
db.delete(key)
return None
return key.value
return None
// 定期删除:每 100ms 扫描
def active_expire_cycle():
# 随机取 20 个带过期时间的 key
keys = random_select(db.expires, 20)
for key in keys:
if key.is_expired():
db.delete(key)
# 如果过期 key 超过 25%,继续扫描
if expired_count > 20 * 0.25:
active_expire_cycle()
五、内存淘汰策略
当 Redis 内存达到上限时,需要淘汰一些 key:
# 配置最大内存
maxmemory 2gb
# 淘汰策略
maxmemory-policy allkeys-lru
八种淘汰策略
| 策略 | 说明 |
|---|---|
| noeviction | 不淘汰,返回错误(默认) |
| volatile-lru | 从设置过期时间的 key 中,淘汰最近最少使用的 |
| allkeys-lru | 从所有 key 中,淘汰最近最少使用的 |
| volatile-lfu | 从设置过期时间的 key 中,淘汰使用频率最低的 |
| allkeys-lfu | 从所有 key 中,淘汰使用频率最低的 |
| volatile-random | 从设置过期时间的 key 中,随机淘汰 |
| allkeys-random | 从所有 key 中,随机淘汰 |
| volatile-ttl | 从设置过期时间的 key 中,淘汰 TTL 最小的 |
LRU vs LFU
# LRU:Least Recently Used,最近最少使用
# 淘汰最长时间没被访问的 key
# LFU:Least Frequently Used,最不经常使用
# 淘汰访问频率最低的 key
六、高频面试题
Q1: Redis 持久化方式如何选择?
推荐方案:
1. 同时开启 RDB 和 AOF
- RDB 用于备份和快速恢复
- AOF 用于数据安全
2. 数据恢复优先级
- AOF 优先(数据更完整)
- 如果 AOF 文件损坏,用 redis-check-aof 修复
3. 性能考虑
- RDB fork 开销大,适合低峰期
- AOF 每秒刷盘,对性能有一定影响
Q2: AOF 和 RDB 可以同时启用吗?
可以!Redis 启动时会优先加载 AOF(数据更完整)
AOF + RDB 组合:
- 数据恢复:优先使用 AOF
- 备份:RDB 文件更紧凑,适合备份
- 性能:主进程只负责写,持久化由子进程完成
Q3: 过期 key 对持久化的影响?
# RDB:生成快照时,已过期的 key 不会被写入
# 但从 RDB 恢复时,会正常加载
# AOF:
# 1. key 过期后,AOF 记录 DEL 命令
# 2. 重写时,已过期的 key 不会被写入新的 AOF 文件
Q4: 如何实现 Redis 缓存和数据库双写一致?
// 方案1:Cache Aside(旁路缓存)
// 读:缓存优先,没有就读数据库,再写缓存
public User getUser(Long id) {
User user = redis.get("user:" + id);
if (user == null) {
user = db.findById(id);
if (user != null) {
redis.set("user:" + id, user, 1小时);
}
}
return user;
}
// 写:先写数据库,再删缓存
public void updateUser(User user) {
db.update(user);
redis.del("user:" + user.getId()); // 删除而非更新
}
// 方案2:延时双删
// 1. 先删缓存
// 2. 写数据库
// 3. 延时一段时间,再删缓存(处理并发问题)
七、最佳实践
# 生产环境配置示例
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
maxmemory 2gb
maxmemory-policy allkeys-lru
# RDB 自动保存
save 900 1
save 300 10
save 60 10000
// Java 客户端使用建议
// 1. 给缓存设置合理的 TTL
redisTemplate.opsForValue().set("key", value, 1, TimeUnit.HOURS);
// 2. 热点数据预热
// 系统启动时,将热点数据加载到缓存
// 3. 监控内存使用
Jedis jedis = new Jedis("localhost", 6379);
String info = jedis.info("memory");
System.out.println(info);
📖 面试真题
Q1: Redis 的持久化方式有哪些?过期策略是什么?
答: Redis 提供了两种主要的持久化方式:RDB 和 AOF,以及多种过期策略。
1. RDB(Redis Database)
- 原理:在指定时间间隔内,将内存中的数据集快照写入磁盘。
- 触发方式:
- 手动触发:
SAVE(阻塞)或BGSAVE(后台异步)。 - 自动触发:配置
save <seconds> <changes>。
- 手动触发:
- 优点:
- 文件紧凑,适合备份和灾难恢复。
- 恢复大数据集时速度比 AOF 快。
- 主从复制时,RDB 文件便于传输。
- 缺点:
- 可能会丢失最后一次快照之后的数据。
- 大数据集时,
BGSAVE可能会占用较多内存。
2. AOF(Append Only File)
- 原理:记录每个写操作命令,以日志形式追加到文件末尾。
- 同步策略:
appendfsync always:每个写命令都同步到磁盘,最安全但性能最差。appendfsync everysec:每秒同步一次,平衡安全性和性能(默认)。appendfsync no:由操作系统决定何时同步,性能最好但可能丢失数据。
- 优点:
- 数据安全性高,最多丢失一秒的数据(使用
everysec)。 - AOF 文件易于理解和解析。
- 数据安全性高,最多丢失一秒的数据(使用
- 缺点:
- 文件体积通常比 RDB 大。
- 恢复速度比 RDB 慢。
3. 混合持久化(Redis 4.0+)
- 原理:结合 RDB 和 AOF 的优点,先使用 RDB 快照,然后之后的写操作使用 AOF 记录。
- 优势:既保证了快速恢复,又减少了数据丢失风险。
4. 过期策略
- 定期删除:Redis 默认每 100ms 随机检查一些设置了过期时间的 key,删除已过期的 key。
- 惰性删除:当客户端尝试访问一个 key 时,检查是否过期,如果过期则删除。
- 内存淘汰策略:当内存不足时,Redis 会根据配置的淘汰策略删除 key:
noeviction:不淘汰,返回错误(默认)。allkeys-lru:从所有 key 中使用 LRU 算法淘汰。volatile-lru:从设置了过期时间的 key 中使用 LRU 算法淘汰。allkeys-random:随机淘汰所有 key。volatile-random:随机淘汰设置了过期时间的 key。volatile-ttl:淘汰过期时间最短的 key。
5. 如何选择?
- 数据安全性要求高:使用 AOF(
appendfsync everysec)或混合持久化。 - 性能要求高,可容忍数据丢失:使用 RDB。
- 内存敏感:根据业务特点选择合适的淘汰策略。
- 实际场景:通常同时开启 RDB 和 AOF,用 RDB 做备份,AOF 保证数据安全。
总结:理解 Redis 持久化和过期策略是保证数据安全性和系统性能的关键,需要根据具体业务需求进行合理配置。
参考链接: