Skip to content
清晨的一缕阳光
返回

Redis 混合持久化实战

Redis 混合持久化实战

Redis 4.0 引入了混合持久化功能,结合了 RDB 和 AOF 的优点:RDB 紧凑、加载快,AOF 数据完整。本文将深入混合持久化的原理和实战应用。

一、混合持久化原理

1.1 为什么需要混合持久化

RDB 和 AOF 的对比:

特性RDBAOF混合持久化
数据完整性低(可能丢失)高高
恢复速度快慢快
文件大小小大中等
性能影响小大中等
混合持久化优势:
┌─────────────────────────────────────┐
│ 前半部分:RDB 格式(数据快照)       │
│ - 紧凑、加载快                       │
│ - 包含历史数据                       │
├─────────────────────────────────────┤
│ 后半部分:AOF 格式(增量命令)       │
│ - 记录快照后的写操作                 │
│ - 保证数据完整性                     │
└─────────────────────────────────────┘

1.2 工作流程

混合持久化流程:
1. 触发 AOF 重写
   ↓
2. fork 子进程
   ↓
3. 子进程写 RDB 快照(前半部分)
   ↓
4. 缓存重写期间的写命令
   ↓
5. 子进程将缓存的命令写入 AOF(后半部分)
   ↓
6. 完成混合持久化文件

时序图:

sequenceDiagram
    participant Parent as 父进程
    participant Child as 子进程
    participant Buffer as 命令缓冲
    participant File as AOF 文件

    Parent->>Parent: 收到 BGREWRITEAOF
    Parent->>Child: fork()
    Child->>File: 写 RDB 快照
    Parent->>Buffer: 缓存写命令
    Child->>Buffer: 读取缓存命令
    Child->>File: 追加 AOF 命令
    Child-->>Parent: 完成信号

二、配置实战

2.1 开启混合持久化

# redis.conf

# 开启 AOF
appendonly yes

# AOF 文件名
appendfilename "appendonly.aof"

# 开启混合持久化(Redis 4.0+)
aof-use-rdb-preamble yes

# AOF 刷盘策略(推荐 everysec)
appendfsync everysec

# AOF 重写触发条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

2.2 配置参数详解

# ==================== AOF 基础配置 ====================

# 是否开启 AOF(默认关闭)
appendonly yes

# AOF 文件名称
appendfilename "appendonly.aof"

# ==================== 混合持久化配置 ====================

# 是否使用 RDB 前缀(默认 no,建议开启)
aof-use-rdb-preamble yes

# ==================== 刷盘策略 ====================

# always:每次写操作都同步到磁盘(最安全,性能最低)
# everysec:每秒同步一次(推荐,折中方案)
# no:由操作系统决定何时同步(性能最高,可能丢失数据)
appendfsync everysec

# ==================== AOF 重写配置 ====================

# AOF 文件增长到多少倍时触发重写
auto-aof-rewrite-percentage 100

# AOF 文件至少多大才触发重写
auto-aof-rewrite-min-size 64mb

# 重写期间是否允许写入(默认 yes)
aof-load-truncated yes

# ==================== 性能优化 ====================

# 禁止在重写期间 fsync(提升性能)
no-appendfsync-on-rewrite no

2.3 不同刷盘策略对比

# 性能测试对比

# appendfsync always
# 写入延迟:~0.5ms
# 数据安全:最高(几乎不丢失)
# 适用场景:金融、支付

# appendfsync everysec(推荐)
# 写入延迟:~1ms
# 数据安全:高(最多丢失 1 秒)
# 适用场景:大多数业务

# appendfsync no
# 写入延迟:~0.1ms
# 数据安全:低(可能丢失大量数据)
# 适用场景:缓存、临时数据

三、实战应用

3.1 Java 实现数据备份

import redis.clients.jedis.Jedis;
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class HybridPersistenceBackup {
    private Jedis jedis;
    private String backupDir;
    
    public HybridPersistenceBackup(Jedis jedis, String backupDir) {
        this.jedis = jedis;
        this.backupDir = backupDir;
    }
    
    /**
     * 触发 AOF 重写(生成混合持久化文件)
     */
    public void triggerAofRewrite() {
        jedis.bgrewriteaof();
        System.out.println("AOF 重写已触发");
    }
    
    /**
     * 等待 AOF 重写完成
     */
    public void waitForRewriteComplete() throws InterruptedException {
        while (true) {
            String info = jedis.info("Persistence");
            if (info.contains("aof_rewrite_in_progress:0")) {
                System.out.println("AOF 重写完成");
                break;
            }
            Thread.sleep(1000);
        }
    }
    
    /**
     * 备份 AOF 文件
     */
    public String backupAofFile() throws IOException {
        String aofPath = jedis.configGet("appenddirname").get(1) + "/appendonly.aof";
        String timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String backupPath = backupDir + "/appendonly_" + timestamp + ".aof";
        
        Files.copy(Paths.get(aofPath), Paths.get(backupPath), 
                   StandardCopyOption.REPLACE_EXISTING);
        
        System.out.println("备份完成:" + backupPath);
        return backupPath;
    }
    
    /**
     * 检查混合持久化状态
     */
    public void checkPersistenceStatus() {
        String info = jedis.info("Persistence");
        System.out.println("=== 持久化状态 ===");
        System.out.println(info);
        
        // 检查是否开启混合持久化
        String config = jedis.configGet("aof-use-rdb-preamble").get(1);
        System.out.println("混合持久化:" + config);
    }
    
    /**
     * 获取 AOF 文件大小
     */
    public long getAofFileSize() {
        String aofPath = jedis.configGet("appenddirname").get(1) + "/appendonly.aof";
        File file = new File(aofPath);
        return file.exists() ? file.length() : 0;
    }
}

3.2 监控混合持久化

import redis.clients.jedis.Jedis;
import java.util.Map;

public class PersistenceMonitor {
    private Jedis jedis;
    
    public PersistenceMonitor(Jedis jedis) {
        this.jedis = jedis;
    }
    
    /**
     * 获取持久化指标
     */
    public Map<String, String> getPersistenceMetrics() {
        return jedis.info("Persistence");
    }
    
    /**
     * 检查 AOF 状态
     */
    public void checkAofStatus() {
        Map<String, String> info = getPersistenceMetrics();
        
        System.out.println("=== AOF 状态 ===");
        System.out.println("AOF 开启:" + info.get("aof_enabled"));
        System.out.println("AOF 文件大小:" + info.get("aof_current_size") + " bytes");
        System.out.println("AOF 基础大小:" + info.get("aof_base_size") + " bytes");
        System.out.println("AOF 重写进行中:" + info.get("aof_rewrite_in_progress"));
        System.out.println("AOF 上次重写时间:" + info.get("aof_last_rewrite_time_sec"));
        System.out.println("AOF 当前重写时间:" + info.get("aof_current_rewrite_time_sec"));
        System.out.println("混合持久化:" + info.get("aof_use_rdb_preamble"));
    }
    
    /**
     * 告警检查
     */
    public void alertCheck() {
        Map<String, String> info = getPersistenceMetrics();
        
        // AOF 文件过大告警
        long aofSize = Long.parseLong(info.get("aof_current_size"));
        if (aofSize > 1024 * 1024 * 1024) { // 1GB
            System.out.println("⚠️  告警:AOF 文件超过 1GB");
        }
        
        // 重写失败告警
        String lastStatus = info.get("aof_last_bgrewrite_status");
        if ("error".equals(lastStatus)) {
            System.out.println("⚠️  告警:上次 AOF 重写失败");
        }
        
        // 重写时间过长告警
        long rewriteTime = Long.parseLong(info.get("aof_last_rewrite_time_sec"));
        if (rewriteTime > 300) { // 5 分钟
            System.out.println("⚠️  告警:AOF 重写时间超过 5 分钟");
        }
    }
}

3.3 数据恢复实战

# ==================== 场景 1:正常恢复 ====================

# 1. 停止 Redis
redis-cli shutdown

# 2. 检查 AOF 文件
ls -lh /var/lib/redis/appendonly.aof

# 3. 启动 Redis(自动加载 AOF)
redis-server /etc/redis/redis.conf

# 4. 验证数据
redis-cli keys "*"
redis-cli dbsize

# ==================== 场景 2:AOF 文件损坏 ====================

# 1. 使用 redis-check-aof 修复
redis-check-aof --fix /var/lib/redis/appendonly.aof

# 2. 确认修复
redis-check-aof /var/lib/redis/appendonly.aof

# 3. 启动 Redis
redis-server /etc/redis/redis.conf

# ==================== 场景 3:手动恢复备份 ====================

# 1. 停止 Redis
redis-cli shutdown

# 2. 备份当前 AOF
mv /var/lib/redis/appendonly.aof /var/lib/redis/appendonly.aof.bak

# 3. 恢复备份文件
cp /backup/appendonly_20251020_120000.aof /var/lib/redis/appendonly.aof

# 4. 启动 Redis
redis-server /etc/redis/redis.conf

四、性能优化

4.1 优化策略

# ==================== 提升写入性能 ====================

# 使用更快的刷盘策略
appendfsync everysec

# 重写期间不 fsync(避免 IO 竞争)
no-appendfsync-on-rewrite yes

# ==================== 控制 AOF 文件大小 ====================

# 合理设置重写阈值
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 128mb

# ==================== 减少 fork 压力 ====================

# 调整系统参数
echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf
sysctl -p

# ==================== 磁盘优化 ====================

# 使用 SSD 硬盘
# 使用 ext4 或 xfs 文件系统
# 挂载选项:noatime,nodiratime

4.2 系统参数调优

# /etc/sysctl.conf

# 允许过度分配内存(避免 fork 失败)
vm.overcommit_memory = 1

# 提升网络性能
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192

# 禁用透明大页(避免延迟)
echo never > /sys/kernel/mm/transparent_hugepage/enabled

# 应用配置
sysctl -p

4.3 监控脚本

#!/bin/bash
# monitor_hybrid_persistence.sh

REDIS_HOST="127.0.0.1"
REDIS_PORT="6379"
ALERT_EMAIL="admin@example.com"

# 获取 AOF 信息
AOF_INFO=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO Persistence)

# 提取关键指标
AOF_ENABLED=$(echo "$AOF_INFO" | grep "aof_enabled" | cut -d: -f2 | tr -d '\r')
AOF_SIZE=$(echo "$AOF_INFO" | grep "aof_current_size" | cut -d: -f2 | tr -d '\r')
AOF_REWRITE=$(echo "$AOF_INFO" | grep "aof_rewrite_in_progress" | cut -d: -f2 | tr -d '\r')

# 告警检查
if [ "$AOF_ENABLED" != "1" ]; then
    echo "⚠️  AOF 未开启"
    exit 1
fi

# AOF 文件大小告警(> 1GB)
if [ "$AOF_SIZE" -gt 1073741824 ]; then
    echo "⚠️  AOF 文件过大:$(($AOF_SIZE / 1024 / 1024)) MB"
    # 发送邮件告警
    # echo "AOF 文件过大" | mail -s "Redis 告警" $ALERT_EMAIL
fi

# 重写时间过长告警
REWRITE_TIME=$(echo "$AOF_INFO" | grep "aof_last_rewrite_time_sec" | cut -d: -f2 | tr -d '\r')
if [ "$REWRITE_TIME" -gt 300 ]; then
    echo "⚠️  AOF 重写时间过长:${REWRITE_TIME}秒"
fi

echo "✓ 混合持久化状态正常"

五、最佳实践

5.1 配置建议

# 生产环境推荐配置

# 开启 AOF
appendonly yes

# 开启混合持久化
aof-use-rdb-preamble yes

# 每秒刷盘(平衡性能和安全)
appendfsync everysec

# 重写阈值
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 128mb

# 重写期间不阻塞
no-appendfsync-on-rewrite yes

5.2 运维建议

  1. 定期检查 AOF 文件大小

    • 超过 1GB 考虑手动重写
    • 监控增长速度
  2. 备份策略

    • 每天备份 AOF 文件
    • 保留 7 天备份
    • 定期测试恢复
  3. 监控指标

    • AOF 文件大小
    • 重写时间
    • 刷盘延迟
  4. 磁盘规划

    • 使用 SSD
    • 预留 3 倍空间
    • 独立磁盘分区

5.3 故障处理

# 问题 1:AOF 文件过大
# 解决:手动触发重写
redis-cli bgrewriteaof

# 问题 2:重写失败
# 解决:检查磁盘空间、权限
df -h
ls -la /var/lib/redis/

# 问题 3:恢复失败
# 解决:使用 redis-check-aof 修复
redis-check-aof --fix appendonly.aof

# 问题 4:性能下降
# 解决:调整刷盘策略
redis-cli config set appendfsync everysec

六、总结

6.1 核心要点

  1. 混合持久化优势

    • 结合 RDB 和 AOF 优点
    • 恢复快、数据完整
    • 文件大小适中
  2. 配置关键

    • 开启 aof-use-rdb-preamble
    • 选择合适刷盘策略
    • 合理设置重写阈值
  3. 性能优化

    • 使用 SSD 硬盘
    • 调整系统参数
    • 监控关键指标

6.2 适用场景

场景推荐配置
金融支付appendfsync always
电商系统appendfsync everysec
社交应用appendfsync everysec
游戏缓存appendfsync no
日志存储appendfsync no + 定期清理

参考资料


分享这篇文章到:

上一篇文章
重读《活着》:苦难中的生命韧性
下一篇文章
Kafka KSQL 流式 SQL 实战指南