HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 学习 Redis

    • Redis 手写实现学习指南
    • 快速开始
    • Redis 架构总览与线程模型
    • RESP 协议与网络通信
    • 事件循环与 I/O 多路复用
    • 底层数据结构设计
    • 字符串与 SDS 实现
    • 哈希表与字典实现
    • 列表与跳表实现
    • 有序集合实现
    • 内存管理与对象系统
    • RDB 持久化机制
    • AOF 持久化机制
    • 混合持久化策略
    • 分布式锁实现
    • 缓存一致性策略
    • 主从复制机制
    • 哨兵模式实现
    • 内存优化与 GC 调优

混合持久化策略

学习目标

  • 深入理解 RDB+AOF 混合持久化原理
  • 掌握启动时的数据加载策略
  • 实现完整的混合持久化管理器
  • 理解数据一致性保证机制
  • 掌握灾难恢复方案和性能调优

混合持久化概述

1. 混合持久化原理

Redis 4.0 引入的混合持久化结合了 RDB 和 AOF 的优势,既保证了数据完整性,又提供了快速的恢复能力:

┌─────────────────────────────────────────────────────────────┐
│                   混合持久化架构                            │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │   RDB 快照  │    │   AOF 日志  │    │  混合文件   │     │
│  │             │    │             │    │             │     │
│  │  ┌───────┐  │    │  ┌───────┐  │    │  ┌───────┐  │     │
│  │  │ 数据  │  │    │  │ 命令  │  │    │  │ RDB   │  │     │
│  │  │ 快照  │  │    │  │ 日志  │  │    │  │ 头部  │  │     │
│  │  └───────┘  │    │  └───────┘  │    │  └───────┘  │     │
│  └─────────────┘    └─────────────┘    │  ┌───────┐  │     │
│           │                   │        │  │ AOF   │  │     │
│           └───────────┬───────┘        │  │ 尾部  │  │     │
│                       │                │  └───────┘  │     │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                混合持久化流程                            │ │
│  │  1. 定期创建 RDB 快照                                   │ │
│  │  2. 记录 AOF 命令日志                                   │ │
│  │  3. 重写时合并为混合文件                                 │ │
│  │  4. 启动时优先加载混合文件                               │ │
│  └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

2. 混合文件格式

混合文件包含 RDB 头部和 AOF 尾部:

[RDB 数据] + [AOF 命令1] + [AOF 命令2] + ... + [AOF 命令N]

格式说明:

  • 文件开头是完整的 RDB 快照
  • 快照后面是 AOF 格式的命令日志
  • 恢复时先加载 RDB,再重放 AOF 命令

️ Go 语言混合持久化实现

1. 混合持久化管理器

// mixed/mixed_persistence.go
package mixed

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
    "sync"
    "time"
)

// 持久化策略
type PersistenceStrategy int

const (
    RDB_ONLY PersistenceStrategy = iota
    AOF_ONLY
    MIXED
)

// 混合持久化管理器
type MixedPersistenceManager struct {
    rdbManager    *RDBManager
    aofManager    *AOFManager
    strategy      PersistenceStrategy
    rdbFilename   string
    aofFilename   string
    mixedFilename string
    mu            sync.RWMutex
    isEnabled     bool
    lastRDBTime   time.Time
    rdbInterval   time.Duration
}

// 创建混合持久化管理器
func NewMixedPersistenceManager(rdbFile, aofFile, mixedFile string, strategy PersistenceStrategy) (*MixedPersistenceManager, error) {
    // 创建 RDB 管理器
    rdbManager, err := NewRDBManager(rdbFile)
    if err != nil {
        return nil, fmt.Errorf("failed to create RDB manager: %v", err)
    }
    
    // 创建 AOF 管理器
    aofManager, err := NewAOFManager(aofFile, AOF_EVERYSEC, 1024*1024)
    if err != nil {
        return nil, fmt.Errorf("failed to create AOF manager: %v", err)
    }
    
    return &MixedPersistenceManager{
        rdbManager:    rdbManager,
        aofManager:    aofManager,
        strategy:      strategy,
        rdbFilename:   rdbFile,
        aofFilename:   aofFile,
        mixedFilename: mixedFile,
        isEnabled:     true,
        lastRDBTime:   time.Now(),
        rdbInterval:   time.Hour, // 默认 1 小时
    }, nil
}

// 写入命令
func (mpm *MixedPersistenceManager) WriteCommand(args []string) error {
    mpm.mu.RLock()
    defer mpm.mu.RUnlock()
    
    if !mpm.isEnabled {
        return nil
    }
    
    // 根据策略写入
    switch mpm.strategy {
    case RDB_ONLY:
        // 只写入 RDB(定期快照)
        return nil
    case AOF_ONLY:
        // 只写入 AOF
        return mpm.aofManager.WriteCommand(args)
    case MIXED:
        // 写入 AOF,定期创建混合文件
        if err := mpm.aofManager.WriteCommand(args); err != nil {
            return err
        }
        
        // 检查是否需要创建混合文件
        if mpm.shouldCreateMixed() {
            go mpm.createMixedFile()
        }
        
        return nil
    }
    
    return nil
}

// 检查是否需要创建混合文件
func (mpm *MixedPersistenceManager) shouldCreateMixed() bool {
    // 检查时间间隔
    if time.Since(mpm.lastRDBTime) < mpm.rdbInterval {
        return false
    }
    
    // 检查 AOF 文件大小
    aofSize, err := mpm.aofManager.GetStatus()
    if err != nil {
        return false
    }
    
    // 如果 AOF 文件超过 100MB,创建混合文件
    if aofSize["size"].(int64) > 100*1024*1024 {
        return true
    }
    
    return false
}

// 创建混合文件
func (mpm *MixedPersistenceManager) createMixedFile() error {
    mpm.mu.Lock()
    defer mpm.mu.Unlock()
    
    // 创建临时混合文件
    tempFile := mpm.mixedFilename + ".tmp"
    
    // 创建 RDB 快照
    if err := mpm.createRDBSnapshot(tempFile); err != nil {
        return fmt.Errorf("failed to create RDB snapshot: %v", err)
    }
    
    // 追加 AOF 命令
    if err := mpm.appendAOFCommands(tempFile); err != nil {
        return fmt.Errorf("failed to append AOF commands: %v", err)
    }
    
    // 原子性替换文件
    if err := mpm.atomicReplace(tempFile); err != nil {
        return fmt.Errorf("failed to replace mixed file: %v", err)
    }
    
    // 更新 RDB 时间
    mpm.lastRDBTime = time.Now()
    
    return nil
}

// 创建 RDB 快照
func (mpm *MixedPersistenceManager) createRDBSnapshot(filename string) error {
    // 这里应该调用实际的 RDB 创建逻辑
    // 简化实现,实际应该遍历所有数据
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 写入 RDB 头部标识
    rdbHeader := []byte("REDIS0001")
    if _, err := file.Write(rdbHeader); err != nil {
        return err
    }
    
    // 这里应该写入实际的 RDB 数据
    // 简化实现,写入一些示例数据
    sampleData := []byte("sample_rdb_data")
    if _, err := file.Write(sampleData); err != nil {
        return err
    }
    
    return nil
}

// 追加 AOF 命令
func (mpm *MixedPersistenceManager) appendAOFCommands(filename string) error {
    // 打开文件追加模式
    file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 读取 AOF 文件
    aofFile, err := os.Open(mpm.aofFilename)
    if err != nil {
        return err
    }
    defer aofFile.Close()
    
    // 复制 AOF 内容
    if _, err := io.Copy(file, aofFile); err != nil {
        return err
    }
    
    return nil
}

// 原子性替换文件
func (mpm *MixedPersistenceManager) atomicReplace(tempFile string) error {
    // 备份原文件
    backupFile := mpm.mixedFilename + ".bak"
    if err := os.Rename(mpm.mixedFilename, backupFile); err != nil {
        // 如果原文件不存在,忽略错误
        if !os.IsNotExist(err) {
            return err
        }
    }
    
    // 重命名临时文件
    if err := os.Rename(tempFile, mpm.mixedFilename); err != nil {
        // 恢复原文件
        if err := os.Rename(backupFile, mpm.mixedFilename); err != nil {
            // 忽略恢复错误
        }
        return err
    }
    
    // 删除备份文件
    os.Remove(backupFile)
    
    return nil
}

2. 数据加载策略

// mixed/data_loading.go
package mixed

import (
    "fmt"
    "io"
    "os"
    "path/filepath"
    "time"
)

// 数据加载器
type DataLoader struct {
    rdbManager    *RDBManager
    aofManager    *AOFManager
    mixedFilename string
    rdbFilename   string
    aofFilename   string
}

// 创建数据加载器
func NewDataLoader(rdbFile, aofFile, mixedFile string) *DataLoader {
    return &DataLoader{
        rdbManager:    NewRDBManager(rdbFile),
        aofManager:    NewAOFManager(aofFile, AOF_EVERYSEC, 1024*1024),
        mixedFilename: mixedFile,
        rdbFilename:   rdbFile,
        aofFilename:   aofFile,
    }
}

// 加载数据
func (dl *DataLoader) LoadData() error {
    // 检查文件存在性
    files := dl.checkFiles()
    
    // 根据文件存在性决定加载策略
    switch files {
    case "mixed":
        return dl.loadMixedFile()
    case "rdb":
        return dl.loadRDBFile()
    case "aof":
        return dl.loadAOFFile()
    case "none":
        return fmt.Errorf("no persistence files found")
    default:
        return fmt.Errorf("unexpected file combination: %s", files)
    }
}

// 检查文件存在性
func (dl *DataLoader) checkFiles() string {
    mixedExists := dl.fileExists(dl.mixedFilename)
    rdbExists := dl.fileExists(dl.rdbFilename)
    aofExists := dl.fileExists(dl.aofFilename)
    
    if mixedExists {
        return "mixed"
    } else if rdbExists && aofExists {
        return "rdb" // 优先加载 RDB
    } else if rdbExists {
        return "rdb"
    } else if aofExists {
        return "aof"
    } else {
        return "none"
    }
}

// 检查文件是否存在
func (dl *DataLoader) fileExists(filename string) bool {
    _, err := os.Stat(filename)
    return !os.IsNotExist(err)
}

// 加载混合文件
func (dl *DataLoader) loadMixedFile() error {
    fmt.Println("Loading mixed persistence file...")
    
    // 打开混合文件
    file, err := os.Open(dl.mixedFilename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 读取 RDB 部分
    if err := dl.loadRDBPart(file); err != nil {
        return fmt.Errorf("failed to load RDB part: %v", err)
    }
    
    // 读取 AOF 部分
    if err := dl.loadAOFPart(file); err != nil {
        return fmt.Errorf("failed to load AOF part: %v", err)
    }
    
    fmt.Println("Mixed persistence file loaded successfully")
    return nil
}

// 加载 RDB 部分
func (dl *DataLoader) loadRDBPart(file *os.File) error {
    // 查找 RDB 结束位置
    rdbEndPos, err := dl.findRDBEndPosition(file)
    if err != nil {
        return err
    }
    
    // 重置文件位置
    if _, err := file.Seek(0, io.SeekStart); err != nil {
        return err
    }
    
    // 读取 RDB 数据
    rdbData := make([]byte, rdbEndPos)
    if _, err := io.ReadFull(file, rdbData); err != nil {
        return err
    }
    
    // 解析 RDB 数据
    if err := dl.parseRDBData(rdbData); err != nil {
        return err
    }
    
    return nil
}

// 查找 RDB 结束位置
func (dl *DataLoader) findRDBEndPosition(file *os.File) (int64, error) {
    // 简化实现,实际应该解析 RDB 格式
    // 这里假设 RDB 部分以特定标识结束
    buffer := make([]byte, 1024)
    var totalRead int64
    
    for {
        n, err := file.Read(buffer)
        if err != nil {
            if err == io.EOF {
                break
            }
            return 0, err
        }
        
        // 查找 RDB 结束标识
        for i := 0; i < n-1; i++ {
            if buffer[i] == 0xFF && buffer[i+1] == 0xFF {
                return totalRead + int64(i) + 2, nil
            }
        }
        
        totalRead += int64(n)
    }
    
    return totalRead, nil
}

// 解析 RDB 数据
func (dl *DataLoader) parseRDBData(data []byte) error {
    // 简化实现,实际应该解析 RDB 格式
    fmt.Printf("Parsing RDB data: %d bytes\n", len(data))
    
    // 这里应该调用实际的 RDB 解析逻辑
    // 例如:dl.rdbManager.Parse(data)
    
    return nil
}

// 加载 AOF 部分
func (dl *DataLoader) loadAOFPart(file *os.File) error {
    // 从当前位置读取 AOF 数据
    aofData, err := io.ReadAll(file)
    if err != nil {
        return err
    }
    
    // 解析 AOF 数据
    if err := dl.parseAOFData(aofData); err != nil {
        return err
    }
    
    return nil
}

// 解析 AOF 数据
func (dl *DataLoader) parseAOFData(data []byte) error {
    // 简化实现,实际应该解析 AOF 格式
    fmt.Printf("Parsing AOF data: %d bytes\n", len(data))
    
    // 这里应该调用实际的 AOF 解析逻辑
    // 例如:dl.aofManager.Parse(data)
    
    return nil
}

// 加载 RDB 文件
func (dl *DataLoader) loadRDBFile() error {
    fmt.Println("Loading RDB file...")
    
    // 调用 RDB 管理器加载
    if err := dl.rdbManager.Load(); err != nil {
        return fmt.Errorf("failed to load RDB file: %v", err)
    }
    
    fmt.Println("RDB file loaded successfully")
    return nil
}

// 加载 AOF 文件
func (dl *DataLoader) loadAOFFile() error {
    fmt.Println("Loading AOF file...")
    
    // 调用 AOF 管理器加载
    if err := dl.aofManager.Load(); err != nil {
        return fmt.Errorf("failed to load AOF file: %v", err)
    }
    
    fmt.Println("AOF file loaded successfully")
    return nil
}

3. 数据一致性保证

// mixed/consistency.go
package mixed

import (
    "crypto/md5"
    "fmt"
    "io"
    "os"
    "sync"
    "time"
)

// 一致性检查器
type ConsistencyChecker struct {
    rdbManager    *RDBManager
    aofManager    *AOFManager
    mixedFilename string
    mu            sync.RWMutex
    lastCheck     time.Time
    checkInterval time.Duration
}

// 创建一致性检查器
func NewConsistencyChecker(rdbManager *RDBManager, aofManager *AOFManager, mixedFile string) *ConsistencyChecker {
    return &ConsistencyChecker{
        rdbManager:    rdbManager,
        aofManager:    aofManager,
        mixedFilename: mixedFile,
        lastCheck:     time.Now(),
        checkInterval: time.Hour, // 默认 1 小时检查一次
    }
}

// 检查数据一致性
func (cc *ConsistencyChecker) CheckConsistency() error {
    cc.mu.Lock()
    defer cc.mu.Unlock()
    
    // 检查时间间隔
    if time.Since(cc.lastCheck) < cc.checkInterval {
        return nil
    }
    
    // 检查 RDB 和 AOF 的一致性
    if err := cc.checkRDBAndAOFConsistency(); err != nil {
        return fmt.Errorf("RDB and AOF consistency check failed: %v", err)
    }
    
    // 检查混合文件的一致性
    if err := cc.checkMixedFileConsistency(); err != nil {
        return fmt.Errorf("mixed file consistency check failed: %v", err)
    }
    
    // 更新检查时间
    cc.lastCheck = time.Now()
    
    return nil
}

// 检查 RDB 和 AOF 的一致性
func (cc *ConsistencyChecker) checkRDBAndAOFConsistency() error {
    // 获取 RDB 数据哈希
    rdbHash, err := cc.getRDBHash()
    if err != nil {
        return err
    }
    
    // 获取 AOF 数据哈希
    aofHash, err := cc.getAOFHash()
    if err != nil {
        return err
    }
    
    // 比较哈希值
    if rdbHash != aofHash {
        return fmt.Errorf("RDB and AOF data mismatch")
    }
    
    return nil
}

// 检查混合文件的一致性
func (cc *ConsistencyChecker) checkMixedFileConsistency() error {
    // 检查混合文件是否存在
    if _, err := os.Stat(cc.mixedFilename); os.IsNotExist(err) {
        return nil // 文件不存在,跳过检查
    }
    
    // 获取混合文件哈希
    mixedHash, err := cc.getMixedFileHash()
    if err != nil {
        return err
    }
    
    // 获取 RDB 和 AOF 的期望哈希
    expectedHash, err := cc.getExpectedMixedHash()
    if err != nil {
        return err
    }
    
    // 比较哈希值
    if mixedHash != expectedHash {
        return fmt.Errorf("mixed file data mismatch")
    }
    
    return nil
}

// 获取 RDB 数据哈希
func (cc *ConsistencyChecker) getRDBHash() (string, error) {
    // 简化实现,实际应该计算 RDB 数据的哈希
    return "rdb_hash", nil
}

// 获取 AOF 数据哈希
func (cc *ConsistencyChecker) getAOFHash() (string, error) {
    // 简化实现,实际应该计算 AOF 数据的哈希
    return "aof_hash", nil
}

// 获取混合文件哈希
func (cc *ConsistencyChecker) getMixedFileHash() (string, error) {
    file, err := os.Open(cc.mixedFilename)
    if err != nil {
        return "", err
    }
    defer file.Close()
    
    hash := md5.New()
    if _, err := io.Copy(hash, file); err != nil {
        return "", err
    }
    
    return fmt.Sprintf("%x", hash.Sum(nil)), nil
}

// 获取期望的混合文件哈希
func (cc *ConsistencyChecker) getExpectedMixedHash() (string, error) {
    // 简化实现,实际应该计算期望的哈希
    return "expected_hash", nil
}

// 修复数据不一致
func (cc *ConsistencyChecker) FixInconsistency() error {
    cc.mu.Lock()
    defer cc.mu.Unlock()
    
    // 检查 RDB 和 AOF 的一致性
    if err := cc.checkRDBAndAOFConsistency(); err != nil {
        // 如果 RDB 和 AOF 不一致,优先使用 AOF
        fmt.Println("RDB and AOF inconsistent, using AOF data")
        return cc.rebuildFromAOF()
    }
    
    // 检查混合文件的一致性
    if err := cc.checkMixedFileConsistency(); err != nil {
        // 如果混合文件不一致,重新创建
        fmt.Println("Mixed file inconsistent, recreating...")
        return cc.recreateMixedFile()
    }
    
    return nil
}

// 从 AOF 重建数据
func (cc *ConsistencyChecker) rebuildFromAOF() error {
    // 这里应该调用 AOF 管理器重建数据
    fmt.Println("Rebuilding data from AOF...")
    return nil
}

// 重新创建混合文件
func (cc *ConsistencyChecker) recreateMixedFile() error {
    // 这里应该调用混合持久化管理器重新创建文件
    fmt.Println("Recreating mixed file...")
    return nil
}

4. 灾难恢复方案

// mixed/disaster_recovery.go
package mixed

import (
    "fmt"
    "os"
    "path/filepath"
    "time"
)

// 灾难恢复管理器
type DisasterRecoveryManager struct {
    rdbManager    *RDBManager
    aofManager    *AOFManager
    mixedManager  *MixedPersistenceManager
    backupDir     string
    retentionDays int
}

// 创建灾难恢复管理器
func NewDisasterRecoveryManager(rdbManager *RDBManager, aofManager *AOFManager, mixedManager *MixedPersistenceManager, backupDir string, retentionDays int) *DisasterRecoveryManager {
    return &DisasterRecoveryManager{
        rdbManager:    rdbManager,
        aofManager:    aofManager,
        mixedManager:  mixedManager,
        backupDir:     backupDir,
        retentionDays: retentionDays,
    }
}

// 创建备份
func (drm *DisasterRecoveryManager) CreateBackup() error {
    // 创建备份目录
    if err := os.MkdirAll(drm.backupDir, 0755); err != nil {
        return fmt.Errorf("failed to create backup directory: %v", err)
    }
    
    // 生成备份文件名
    timestamp := time.Now().Format("20060102_150405")
    rdbBackup := filepath.Join(drm.backupDir, fmt.Sprintf("dump_%s.rdb", timestamp))
    aofBackup := filepath.Join(drm.backupDir, fmt.Sprintf("appendonly_%s.aof", timestamp))
    mixedBackup := filepath.Join(drm.backupDir, fmt.Sprintf("mixed_%s.rdb", timestamp))
    
    // 备份 RDB 文件
    if err := drm.backupFile(drm.rdbManager.GetFilename(), rdbBackup); err != nil {
        return fmt.Errorf("failed to backup RDB file: %v", err)
    }
    
    // 备份 AOF 文件
    if err := drm.backupFile(drm.aofManager.GetFilename(), aofBackup); err != nil {
        return fmt.Errorf("failed to backup AOF file: %v", err)
    }
    
    // 备份混合文件
    if err := drm.backupFile(drm.mixedManager.GetMixedFilename(), mixedBackup); err != nil {
        return fmt.Errorf("failed to backup mixed file: %v", err)
    }
    
    // 清理过期备份
    if err := drm.cleanupExpiredBackups(); err != nil {
        return fmt.Errorf("failed to cleanup expired backups: %v", err)
    }
    
    return nil
}

// 备份文件
func (drm *DisasterRecoveryManager) backupFile(src, dst string) error {
    // 检查源文件是否存在
    if _, err := os.Stat(src); os.IsNotExist(err) {
        return nil // 文件不存在,跳过备份
    }
    
    // 复制文件
    srcFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dstFile.Close()
    
    if _, err := io.Copy(dstFile, srcFile); err != nil {
        return err
    }
    
    return nil
}

// 清理过期备份
func (drm *DisasterRecoveryManager) cleanupExpiredBackups() error {
    // 获取备份目录中的所有文件
    files, err := os.ReadDir(drm.backupDir)
    if err != nil {
        return err
    }
    
    // 计算过期时间
    cutoffTime := time.Now().AddDate(0, 0, -drm.retentionDays)
    
    // 删除过期文件
    for _, file := range files {
        if file.IsDir() {
            continue
        }
        
        info, err := file.Info()
        if err != nil {
            continue
        }
        
        if info.ModTime().Before(cutoffTime) {
            filePath := filepath.Join(drm.backupDir, file.Name())
            if err := os.Remove(filePath); err != nil {
                fmt.Printf("Failed to remove expired backup file %s: %v\n", filePath, err)
            }
        }
    }
    
    return nil
}

// 恢复数据
func (drm *DisasterRecoveryManager) RestoreData(backupType string, timestamp string) error {
    var filename string
    
    switch backupType {
    case "rdb":
        filename = filepath.Join(drm.backupDir, fmt.Sprintf("dump_%s.rdb", timestamp))
    case "aof":
        filename = filepath.Join(drm.backupDir, fmt.Sprintf("appendonly_%s.aof", timestamp))
    case "mixed":
        filename = filepath.Join(drm.backupDir, fmt.Sprintf("mixed_%s.rdb", timestamp))
    default:
        return fmt.Errorf("unsupported backup type: %s", backupType)
    }
    
    // 检查备份文件是否存在
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        return fmt.Errorf("backup file not found: %s", filename)
    }
    
    // 恢复文件
    if err := drm.restoreFile(filename, backupType); err != nil {
        return fmt.Errorf("failed to restore file: %v", err)
    }
    
    return nil
}

// 恢复文件
func (drm *DisasterRecoveryManager) restoreFile(src, backupType string) error {
    var dst string
    
    switch backupType {
    case "rdb":
        dst = drm.rdbManager.GetFilename()
    case "aof":
        dst = drm.aofManager.GetFilename()
    case "mixed":
        dst = drm.mixedManager.GetMixedFilename()
    default:
        return fmt.Errorf("unsupported backup type: %s", backupType)
    }
    
    // 复制文件
    srcFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer srcFile.Close()
    
    dstFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dstFile.Close()
    
    if _, err := io.Copy(dstFile, srcFile); err != nil {
        return err
    }
    
    return nil
}

// 列出可用备份
func (drm *DisasterRecoveryManager) ListBackups() ([]BackupInfo, error) {
    files, err := os.ReadDir(drm.backupDir)
    if err != nil {
        return nil, err
    }
    
    var backups []BackupInfo
    
    for _, file := range files {
        if file.IsDir() {
            continue
        }
        
        info, err := file.Info()
        if err != nil {
            continue
        }
        
        // 解析文件名获取类型和时间戳
        backupType, timestamp := drm.parseBackupFilename(file.Name())
        if backupType != "" {
            backups = append(backups, BackupInfo{
                Type:      backupType,
                Timestamp: timestamp,
                Size:      info.Size(),
                ModTime:   info.ModTime(),
            })
        }
    }
    
    return backups, nil
}

// 解析备份文件名
func (drm *DisasterRecoveryManager) parseBackupFilename(filename string) (string, string) {
    // 简化实现,实际应该使用正则表达式
    if len(filename) < 4 {
        return "", ""
    }
    
    if filename[:4] == "dump" {
        return "rdb", filename[5:15] // 假设时间戳格式为 YYYYMMDD_HH
    } else if filename[:10] == "appendonly" {
        return "aof", filename[11:21]
    } else if filename[:6] == "mixed" {
        return "mixed", filename[7:17]
    }
    
    return "", ""
}

// 备份信息
type BackupInfo struct {
    Type      string
    Timestamp string
    Size      int64
    ModTime   time.Time
}

测试验证

1. 单元测试

// mixed/mixed_persistence_test.go
package mixed

import (
    "os"
    "testing"
    "time"
)

func TestMixedPersistenceManager(t *testing.T) {
    rdbFile := "test.rdb"
    aofFile := "test.aof"
    mixedFile := "test_mixed.rdb"
    
    defer os.Remove(rdbFile)
    defer os.Remove(aofFile)
    defer os.Remove(mixedFile)
    
    manager, err := NewMixedPersistenceManager(rdbFile, aofFile, mixedFile, MIXED)
    if err != nil {
        t.Fatalf("Failed to create mixed persistence manager: %v", err)
    }
    defer manager.Close()
    
    // 写入测试命令
    commands := [][]string{
        {"SET", "key1", "value1"},
        {"SET", "key2", "value2"},
        {"HSET", "hash1", "field1", "value1"},
    }
    
    for _, cmd := range commands {
        if err := manager.WriteCommand(cmd); err != nil {
            t.Fatalf("Failed to write command: %v", err)
        }
    }
    
    // 等待一段时间
    time.Sleep(2 * time.Second)
    
    // 获取状态
    status := manager.GetStatus()
    if !status["enabled"].(bool) {
        t.Error("Mixed persistence should be enabled")
    }
}

func TestDataLoader(t *testing.T) {
    rdbFile := "test.rdb"
    aofFile := "test.aof"
    mixedFile := "test_mixed.rdb"
    
    defer os.Remove(rdbFile)
    defer os.Remove(aofFile)
    defer os.Remove(mixedFile)
    
    loader := NewDataLoader(rdbFile, aofFile, mixedFile)
    
    // 测试加载数据
    if err := loader.LoadData(); err != nil {
        // 如果没有文件,这是预期的
        if err.Error() != "no persistence files found" {
            t.Fatalf("Unexpected error: %v", err)
        }
    }
}

func TestConsistencyChecker(t *testing.T) {
    rdbManager := NewRDBManager("test.rdb")
    aofManager := NewAOFManager("test.aof", AOF_EVERYSEC, 1024*1024)
    checker := NewConsistencyChecker(rdbManager, aofManager, "test_mixed.rdb")
    
    // 测试一致性检查
    if err := checker.CheckConsistency(); err != nil {
        // 如果没有文件,这是预期的
        if err.Error() != "RDB and AOF data mismatch" {
            t.Fatalf("Unexpected error: %v", err)
        }
    }
}

func TestDisasterRecoveryManager(t *testing.T) {
    backupDir := "test_backup"
    defer os.RemoveAll(backupDir)
    
    rdbManager := NewRDBManager("test.rdb")
    aofManager := NewAOFManager("test.aof", AOF_EVERYSEC, 1024*1024)
    mixedManager := NewMixedPersistenceManager("test.rdb", "test.aof", "test_mixed.rdb", MIXED)
    
    drm := NewDisasterRecoveryManager(rdbManager, aofManager, mixedManager, backupDir, 7)
    
    // 测试创建备份
    if err := drm.CreateBackup(); err != nil {
        t.Fatalf("Failed to create backup: %v", err)
    }
    
    // 测试列出备份
    backups, err := drm.ListBackups()
    if err != nil {
        t.Fatalf("Failed to list backups: %v", err)
    }
    
    if len(backups) == 0 {
        t.Error("Expected at least one backup")
    }
}

2. 性能基准测试

// mixed/benchmark_test.go
package mixed

import (
    "os"
    "testing"
    "time"
)

func BenchmarkMixedPersistenceWrite(b *testing.B) {
    rdbFile := "benchmark.rdb"
    aofFile := "benchmark.aof"
    mixedFile := "benchmark_mixed.rdb"
    
    defer os.Remove(rdbFile)
    defer os.Remove(aofFile)
    defer os.Remove(mixedFile)
    
    manager, err := NewMixedPersistenceManager(rdbFile, aofFile, mixedFile, MIXED)
    if err != nil {
        b.Fatalf("Failed to create mixed persistence manager: %v", err)
    }
    defer manager.Close()
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        command := []string{"SET", "key", "value"}
        manager.WriteCommand(command)
    }
}

func BenchmarkMixedPersistenceLoad(b *testing.B) {
    rdbFile := "benchmark.rdb"
    aofFile := "benchmark.aof"
    mixedFile := "benchmark_mixed.rdb"
    
    defer os.Remove(rdbFile)
    defer os.Remove(aofFile)
    defer os.Remove(mixedFile)
    
    // 预填充文件
    manager, err := NewMixedPersistenceManager(rdbFile, aofFile, mixedFile, MIXED)
    if err != nil {
        b.Fatalf("Failed to create mixed persistence manager: %v", err)
    }
    
    for i := 0; i < 1000; i++ {
        command := []string{"SET", "key", "value"}
        manager.WriteCommand(command)
    }
    manager.Close()
    
    // 加载测试
    loader := NewDataLoader(rdbFile, aofFile, mixedFile)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        loader.LoadData()
    }
}

func BenchmarkConsistencyCheck(b *testing.B) {
    rdbManager := NewRDBManager("benchmark.rdb")
    aofManager := NewAOFManager("benchmark.aof", AOF_EVERYSEC, 1024*1024)
    checker := NewConsistencyChecker(rdbManager, aofManager, "benchmark_mixed.rdb")
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        checker.CheckConsistency()
    }
}

func BenchmarkDisasterRecovery(b *testing.B) {
    backupDir := "benchmark_backup"
    defer os.RemoveAll(backupDir)
    
    rdbManager := NewRDBManager("benchmark.rdb")
    aofManager := NewAOFManager("benchmark.aof", AOF_EVERYSEC, 1024*1024)
    mixedManager := NewMixedPersistenceManager("benchmark.rdb", "benchmark.aof", "benchmark_mixed.rdb", MIXED)
    
    drm := NewDisasterRecoveryManager(rdbManager, aofManager, mixedManager, backupDir, 7)
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        drm.CreateBackup()
    }
}

3. 集成测试

// mixed/integration_test.go
package mixed

import (
    "os"
    "testing"
    "time"
)

func TestMixedPersistenceIntegration(t *testing.T) {
    rdbFile := "integration.rdb"
    aofFile := "integration.aof"
    mixedFile := "integration_mixed.rdb"
    
    defer os.Remove(rdbFile)
    defer os.Remove(aofFile)
    defer os.Remove(mixedFile)
    
    // 创建混合持久化管理器
    manager, err := NewMixedPersistenceManager(rdbFile, aofFile, mixedFile, MIXED)
    if err != nil {
        t.Fatalf("Failed to create mixed persistence manager: %v", err)
    }
    defer manager.Close()
    
    // 写入测试数据
    testCommands := [][]string{
        {"SET", "key1", "value1"},
        {"SET", "key2", "value2"},
        {"HSET", "hash1", "field1", "value1"},
        {"LPUSH", "list1", "item1", "item2"},
        {"ZADD", "zset1", "1", "member1"},
    }
    
    for _, cmd := range testCommands {
        if err := manager.WriteCommand(cmd); err != nil {
            t.Fatalf("Failed to write command: %v", err)
        }
    }
    
    // 等待同步
    time.Sleep(2 * time.Second)
    
    // 创建数据加载器
    loader := NewDataLoader(rdbFile, aofFile, mixedFile)
    
    // 测试数据加载
    if err := loader.LoadData(); err != nil {
        t.Fatalf("Failed to load data: %v", err)
    }
    
    // 创建一致性检查器
    rdbManager := NewRDBManager(rdbFile)
    aofManager := NewAOFManager(aofFile, AOF_EVERYSEC, 1024*1024)
    checker := NewConsistencyChecker(rdbManager, aofManager, mixedFile)
    
    // 测试一致性检查
    if err := checker.CheckConsistency(); err != nil {
        t.Fatalf("Consistency check failed: %v", err)
    }
    
    // 创建灾难恢复管理器
    backupDir := "integration_backup"
    defer os.RemoveAll(backupDir)
    
    drm := NewDisasterRecoveryManager(rdbManager, aofManager, manager, backupDir, 7)
    
    // 测试备份
    if err := drm.CreateBackup(); err != nil {
        t.Fatalf("Failed to create backup: %v", err)
    }
    
    // 测试恢复
    backups, err := drm.ListBackups()
    if err != nil {
        t.Fatalf("Failed to list backups: %v", err)
    }
    
    if len(backups) == 0 {
        t.Error("Expected at least one backup")
    }
}

性能对比分析

1. 持久化策略对比

策略数据完整性恢复速度文件大小性能影响适用场景
RDB中等快小低定期备份
AOF高慢大中等实时持久化
混合高快中等中等生产环境

2. 恢复策略对比

策略恢复时间数据丢失实现复杂度适用场景
RDB 优先快中等低性能优先
AOF 优先慢少低数据优先
混合优先快少高平衡

3. 性能测试结果

// 基准测试结果示例
func BenchmarkComparison(b *testing.B) {
    // 混合持久化性能
    b.Run("MixedPersistence", func(b *testing.B) {
        manager, _ := NewMixedPersistenceManager("test.rdb", "test.aof", "test_mixed.rdb", MIXED)
        defer manager.Close()
        
        for i := 0; i < b.N; i++ {
            command := []string{"SET", "key", "value"}
            manager.WriteCommand(command)
        }
    })
    
    // RDB 持久化性能
    b.Run("RDBPersistence", func(b *testing.B) {
        manager, _ := NewMixedPersistenceManager("test.rdb", "test.aof", "test_mixed.rdb", RDB_ONLY)
        defer manager.Close()
        
        for i := 0; i < b.N; i++ {
            command := []string{"SET", "key", "value"}
            manager.WriteCommand(command)
        }
    })
    
    // AOF 持久化性能
    b.Run("AOFPersistence", func(b *testing.B) {
        manager, _ := NewMixedPersistenceManager("test.rdb", "test.aof", "test_mixed.rdb", AOF_ONLY)
        defer manager.Close()
        
        for i := 0; i < b.N; i++ {
            command := []string{"SET", "key", "value"}
            manager.WriteCommand(command)
        }
    })
}

面试要点

1. 混合持久化的优势

答案要点:

  • 数据完整性:结合 RDB 和 AOF 的优势
  • 恢复速度:RDB 提供快速恢复,AOF 提供数据完整性
  • 存储效率:混合文件比纯 AOF 更小
  • 性能平衡:在性能和安全性之间取得平衡

2. 数据加载策略

答案要点:

  • 优先级:混合文件 > RDB 文件 > AOF 文件
  • 完整性:优先选择数据最完整的文件
  • 性能:优先选择恢复速度最快的文件
  • 一致性:确保加载的数据是一致的

3. 数据一致性保证

答案要点:

  • 定期检查:定期检查文件一致性
  • 哈希验证:使用哈希值验证数据完整性
  • 自动修复:发现不一致时自动修复
  • 监控告警:不一致时发送告警

4. 灾难恢复方案

答案要点:

  • 定期备份:定期创建数据备份
  • 多地点存储:备份存储在不同地点
  • 快速恢复:提供快速恢复机制
  • 数据验证:恢复后验证数据完整性

总结

通过本章学习,我们深入理解了:

  1. 混合持久化的基本原理和架构设计
  2. 数据加载策略的实现和优化
  3. 数据一致性保证机制和检查方法
  4. 灾难恢复方案的完整实现
  5. 性能调优技巧和最佳实践

混合持久化为 Redis 提供了最佳的数据持久化方案,既保证了数据完整性,又提供了快速的恢复能力。在下一章中,我们将学习缓存一致性策略,了解如何保证缓存和数据库的数据一致性。

Prev
AOF 持久化机制
Next
分布式锁实现