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 实战指南