Redis持久化机制详解

发布于:2025-06-06 ⋅ 阅读:(20) ⋅ 点赞:(0)

Redis持久化机制详解

前言

Redis提供两种持久化机制:RDB和AOF,用于将内存中的数据保存到磁盘。

  • RDB:定时将所有键值对以二进制压缩数据形式写入rdb文件,是数据库状态的快照,属于全量备份
  • AOF:将Redis执行的写入命令按协议格式以纯文本形式记录到AOF文件中,属于增量备份

RDB持久化机制

rdb机制会定时将所有键值对,以二进制压缩数据的形式写入rdb文件中,其实就是给当前数据库状态做了一个全量备份,恢复数据的时候,只需要将rdb文件加载到内存中即可,优点是rdb文件采用了二进制压缩,文件体积较小,恢复速度快,所以rdb机制适合大规模的数据备份和恢复,但是由于是定时写入rdb文件,如果在两次写入期间redis发生了宕机的话,就会造成最后一次rdb之后的数据全部丢失,适合于对于数据完整性要求不高的业务场景

1.1 RDB文件的创建与载入

创建RDB文件的命令

SAVE命令

  • 阻塞redis服务器进程,直到rdb文件创建完毕
  • 在阻塞期间,redis服务器进程不能处理任何命令请求

BGSAVE命令

  • fork出子进程负责创建rdb文件
  • 父进程继续处理命令请求,不会阻塞
  • 写时复制:fork出的子进程和父进程共享相同的内存数据,当父进程需要写入或者修改数据的时候,会将内存数据复制出一个副本,然后在这个副本上进行修改,避免了子进程和父进程由于共享内存数据导致的数据安全问题,所以子进程创建的rdb文件只会保存fork出子进程那一刻的数据库状态
  • 子进程先把内存中的数据写入到一个临时文件中,写入完毕之后,再用这个临时文件取代原来的rdb文件
实现原理
def SAVE():
    rdbSave()  # 创建RDB文件

def BGSAVE():
    pid = fork()  # 创建子进程
    if pid == 0:
        rdbSave()  # 子进程负责创建RDB文件
        signal_parent()  # 完成后向父进程发送信号
    elif pid > 0:
        handle_request_and_wait_signal()  # 父进程继续处理请求
    else:
        handle_fork_error()  # 处理出错情况
RDB文件载入
  • 服务器启动时自动将rdb文件加载到内存中
  • 因为是程序自动完成的,所以redis没有提供专门的载入命令
  • 服务器启动时检测到RDB文件存在,就会自动载入

1.2 定期保存到rdb文件

保存条件设置

通过save选项设置保存条件,默认配置:

save 900 1     # 900秒内至少1次修改
save 300 10    # 300秒内至少10次修改
save 60 10000  # 60秒内至少10000次修改
服务器状态结构
struct redisServer {
    struct saveparam *saveparams;  // 保存条件数组
    long long dirty;               // 修改计数器
    time_t lastsave;              // 上次保存时间
};

struct saveparam {
    time_t seconds;  // 秒数
    int changes;     // 修改数
};
检查机制
  • serverCron函数每100毫秒执行一次
  • 遍历所有保存条件,满足任一条件就执行BGSAVE
def serverCron():
    for saveparam in server.saveparams:
        save_interval = unixtime_now() - server.lastsave
        if server.dirty >= saveparam.changes and save_interval > saveparam.seconds:
            BGSAVE()

AOF持久化机制

aof机制会将redis所执行的写入命令,按照redis请求协议的格式,以纯文本的形式追加到aof文件末尾,优点是最多丢失一秒的数据,更能保证数据的安全性,缺点是aof文件的体积较大,需要定时重写,所以aof适合于对数据完整性和一致性要求特别高的业务

2.1 AOF持久化的实现步骤

AOF持久化分为三个步骤:命令追加文件写入文件同步

2.1.1 命令追加
  • 当AOF功能开启时,服务器执行写命令后,会将命令以协议格式追加到aof_buf缓冲区
struct redisServer {
    sds aof_buf;  // AOF缓冲区
};

示例

  • 执行SET KEY VALUE后,追加内容:

    *3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
    
2.1.2 文件写入与同步

在每个事件循环结束前,调用flushAppendOnlyFile函数:

def eventLoop():
    while True:
        processFileEvents()    # 处理文件事件
        processTimeEvents()    # 处理时间事件
        flushAppendOnlyFile()  # 考虑写入AOF文件
appendfsync选项
选项值 行为描述 效率 安全性
always 每次事件循环都写入并同步 最慢 最安全,最多丢失一个事件循环的数据
everysec 每次写入,每秒同步一次 较快 较安全,最多丢失一秒数据
no 每次写入,同步由操作系统决定 最快 安全性最低

2.2 AOF文件重写

2.2.1 重写的必要性

随着服务器运行,AOF文件会越来越大,其中就包含了很多对于恢复数据库状态来说是不必要的命令,所以要定时根据当前的数据库状态,重写aof文件,只保留恢复当前数据库状态的所必须的最少命令

2.2.2 重写实现原理

不需要根据现有AOF文件来重写,而是直接按照当前的数据库状态来编写命令

例如,原来aof文件中的针对同一个key的多条写入命令就完全可以简化成一条命令,只要保证命令执行之后得到的结果是一样的就可以了

# 原AOF文件可能有多条命令
RPUSH list "A" "B"
RPUSH list "C"
LPOP list
RPUSH list "D"

# 重写后只需一条命令
RPUSH list "B" "C" "D"
2.2.3 重写算法
def aof_rewrite(new_aof_file_name):
    f = create_file(new_aof_file_name)
    
    for db in redisServer.db:
        if db.is_empty(): continue
        f.write_command("SELECT", db.id)
        
        for key in db:
            if key.is_expired(): continue
            
            # 根据键类型重写
            if key.type == String:
                rewrite_string(key)
            elif key.type == List:
                rewrite_list(key)
            # ... 其他类型
            
            # 重写过期时间
            if key.have_expire_time():
                rewrite_expire_time(key)
    
    f.close()
2.2.4 后台重写(BGREWRITEAOF)

由于aof重写的频率是比较高的,所以应该要在后台进行,避免阻塞redis服务器进程,但是如果重写期间服务器继续处理命令,就会出现数据不一致的问题,这个时候就可以让父进程将新命令同时写入aof_buf缓冲区和aof重写缓冲区

解决方案:AOF重写缓冲区

  • 子进程执行重写时,父进程将新命令同时写入:
    1. AOF缓冲区(正常AOF文件)
    2. AOF重写缓冲区

重写流程

  1. 创建子进程执行重写
  2. 父进程继续处理命令,同时写入两个缓冲区
  3. 子进程完成重写后发送信号
  4. 父进程将重写缓冲区内容追加到新AOF文件
  5. 原子性替换旧AOF文件

RDB vs AOF对比

特性 RDB AOF
文件大小 小(二进制压缩) 大(纯文本命令)
恢复速度
数据安全性 可能丢失两次保存间的数据 最多丢失1秒数据
对性能影响 BGSAVE对性能影响小 写入频繁时影响较大
备份类型 全量备份 增量备份