开发易忽视的问题:Redis持久化设计与实现

642 阅读16分钟

Redis是一种高性能的key-value数据库,支持数据的持久化存储。Redis主要提供两种持久化机制:RDB(Redis Database)和AOF(Append Only File)。默认情况下,Redis 使用 RDB 作为其持久化机制。下面是对这两种持久化机制的详细介绍及其实现原理:

RDB(Redis Database)

设计理念

RDB通过快照方式将某一时刻的数据保存到磁盘中。它可以在指定的时间间隔内生成数据库文件(dump.rdb),这种方式适合于做定期备份。

实现细节

  1. 生成快照:Redis会在以下几种情况下生成一个RDB文件:

    • 用户执行SAVEBGSAVE命令。
    • 配置文件设置了save选项,当满足设定的条件时自动触发。
  2. SAVE与BGSAVE

    • SAVE:阻塞当前Redis服务器进程,直到RDB文件创建完毕。
    • BGSAVE:创建子进程,由子进程来完成快照工作,主进程继续处理客户端请求。
  3. 压缩:为了减少存储空间,RDB文件中的数据采用LZF算法进行压缩。

  4. 恢复数据:当Redis启动时,可以通过加载RDB文件快速恢复数据。

优点

  • 快照文件体积较小,适合用于备份。
  • 恢复速度较快。

缺点

  • 数据可能在两次快照之间丢失。
  • 大数据集时创建快照可能会影响性能。

AOF(Append Only File)

设计理念

AOF通过记录每个写操作日志实现持久化,相比RDB更加实时持久化,能更好地保证数据不丢失。

实现细节

  1. 命令追加:每次写操作都会被追加到AOF文件中(appendonly.aof)。

  2. 重写机制:为了防止AOF文件过大,Redis支持AOF文件重写(Rewrite)。重写操作会生成一个新的AOF文件,包含相同的数据但文件体积更小。

    • 重写由BGREWRITEAOF命令触发,也可以根据配置自动触发,例如当AOF文件增长到一定大小时。
    • Redis会创建一个子进程,子进程先生成一个新的AOF文件,主进程仍然处理新命令并将这些命令缓存在内存中,待子进程完成后再将内存中的新命令写入新的AOF文件中,并替换旧的AOF文件。
  3. 恢复数据:Redis启动时,会从AOF文件中逐条重放写操作来恢复数据。

优点

  • 更加实时的持久化,默认每秒fsync一次,通过配置可以改为每次写操作都fsync。
  • 更好的数据安全性,能够最大程度减少数据丢失。

缺点

  • AOF文件通常比RDB文件大。
  • 恢复速度比RDB稍慢,因为需要逐条重放写操作。

RDB与AOF混合使用

Redis允许同时使用RDB和AOF持久化机制,以利用二者的优点:

  • 定期生成RDB快照用于快速备份。
  • 使用AOF保证数据的实时持久化和安全性。

配置文件中可以设置相关参数,确保两种机制的协同工作。

小结

  • RDB:适合定期备份、快速恢复,但可能存在数据丢失风险。
  • AOF:保障数据完整性,适合高可靠性要求的场景,但文件较大且恢复速度较慢。

RDB文件结构

RDB(Redis Database)文件是Redis用于存储数据快照的文件格式。RDB文件结构紧凑、读取速度快,使得它非常适合用于备份和数据恢复。下面详细介绍RDB文件的内部结构及其设计原理。

RDB文件结构

RDB文件包含多个部分,每个部分依次存储在文件中。整体结构如下:

  1. 文件头 (Header)

    • 包含一个固定的字符串标识:REDIS
    • 后接一个数字,表示RDB文件的版本号,例如0006表示版本6。
  2. 数据库元数据 (Database Metadata)

    • 记录了数据库的数量以及每个数据库的编号(index)。
  3. 键值对数据 (Key-Value Pairs)

    • 每个数据库中的键值对数据,包括键、类型、过期时间和实际的数据内容。
    • 数据按照数据库编号依次存储。
  4. 特殊标记

    • 在键值对序列结束后,有一个特殊的EOF(End of File)标记,用于指示数据段的结束。
  5. 校验码 (Checksum)

    • 用于校验RDB文件的完整性。
    • 是一个8字节的CRC64校验码。

键值对数据部分的详细结构

对于每个键值对数据部分,结构如下:

  1. 选择数据库命令 (Select DB)

    • 标志:使用特定表示符,如0xFE
    • 数据库编号:紧跟在标志后面的一个整型数,表示当前数据属于哪个数据库。
  2. 过期时间 (Expire Time)

    • Redis会为每个即将过期的键记录过期时间。
    • 可以以毫秒或秒为单位存储,分别用不同的标志符,如0xFD(毫秒)和0xFC(秒)。
    • 过期时间之后是实际的时间值。
  3. 键值对 (Key-Value Pair)

    • 类型:一个字节表示键的数据类型,如字符串、列表、集合、有序集合和哈希等。
    • 键:一个长度前缀的字符串。
    • 值:根据键的类型不同,存储的方式也会有所不同。

示例

假设一个RDB文件保存了以下两个键值对:

  1. 数据库0:

    • Key: name,Value: GPT,Type: string
    • Key: age,Value: 3,Type: integer
  2. 数据库1:

    • Key: students,Value: ["Alice", "Bob"],Type: list

其大致布局如下:

+---------+------+
| Header  | REDIS0006 |
+---------+------+
| Select DB | 0    |
+---------+------+
| Key-Val | name, GPT (string) |
+---------+------+
| Key-Val | age, 3 (integer)   |
+---------+------+
| Select DB | 1    |
+---------+------+
| Key-Val | students, ["Alice", "Bob"] (list) |
+---------+------+
| EOF     |      |
+---------+------+
| Checksum| CRC64 |
+---------+------+

文件头和版本

  • REDIS: 固定的5个字节。
  • 0006: 4个字节表示版本号。

校验码

文件末尾的8字节校验码(CRC64)确保文件在存储和传输过程中未被篡改。

小结

RDB文件结构通过一种紧凑的二进制格式存储Redis数据库的快照,具备高效的读写性能。理解RDB文件的结构有助于掌握Redis持久化机制,为优化和维护数据库提供理论基础。

RDB备份策略

RDB(Redis Database)备份策略是确保数据安全和可恢复性的重要组成部分。通过合理的备份策略,可以有效应对数据丢失、灾难恢复等情况。下面是Redis RDB备份策略及其具体实现方法。

RDB备份策略

  1. 定期备份

    • 定时生成RDB快照文件,例如每天或每小时进行一次备份。
    • 根据数据的重要性和变化频率调整备份间隔。
  2. 多层次备份

    • 结合日常备份和周备份,保留多个历史版本。
    • 可以采用全量备份和增量备份相结合的方法。
  3. 异地备份

    • 将备份文件存储在异地服务器或云存储上,防止单一故障点导致的数据丢失。
  4. 自动化管理

    • 使用脚本或工具自动执行备份任务。
    • 设置监控和告警机制,确保备份过程顺利进行。

RDB具体实现

配置文件设置

在Redis配置文件redis.conf中,通过save指令可以设置自动保存RDB快照的规则。例如:

# 每900秒(15分钟)如果有1个键被修改,则触发一次RDB快照
save 900 1
# 每300秒(5分钟)如果有10个键被修改,则触发一次RDB快照
save 300 10
# 每60秒(1分钟)如果有10000个键被修改,则触发一次RDB快照
save 60 10000

这些配置项表示,当指定时间内有足够数量的写操作发生时,Redis会触发RDB快照存储。

手动触发备份

通过命令行手动触发RDB备份:

  • SAVE:阻塞当前Redis实例,直到RDB文件创建完毕。这适用于小型数据集,并且在非繁忙时段使用。

    SAVE
    
  • BGSAVE:创建子进程完成RDB文件的保存工作,主进程继续服务客户端请求。推荐在生产环境中使用,以减少对服务的影响。

    BGSAVE
    

备份脚本示例

可以编写一个简单的备份脚本,结合crontab进行定时备份(一般不需要手动编写,redis通过配置可以完成以下操作):

#!/bin/bash

# 定义备份目录
BACKUP_DIR="/path/to/backup"
DATE=$(date +%Y%m%d_%H%M%S)
RDB_FILE="dump-${DATE}.rdb"

# 执行BGSAVE命令
redis-cli BGSAVE

# 等待BGSAVE完成
sleep 10

# 将RDB文件复制到备份目录
cp /path/to/redis/dump.rdb ${BACKUP_DIR}/${RDB_FILE}

# 保留最近7天的备份,删除旧备份
find ${BACKUP_DIR} -type f -mtime +7 -name '*.rdb' -exec rm {} ;

将此脚本保存为backup.sh,并设置crontab任务:

0 */1 * * * /path/to/backup.sh

上述设置表示每小时执行一次备份。

异地备份

利用rsync或scp将备份文件传输到远程服务器:

#!/bin/bash

# 本地备份目录和远程服务器信息
LOCAL_BACKUP_DIR="/path/to/backup"
REMOTE_SERVER="user@remote_server:/path/to/remote_backup"

# 同步备份文件到远程服务器
rsync -avz ${LOCAL_BACKUP_DIR}/ ${REMOTE_SERVER}

backup.sh脚本的最后添加上述同步命令,即可实现异地备份。

RDB数据恢复流程

在 Redis 3.0 中,当服务器异常恢复重启时,通常并不需要手动编写脚本来执行 RDB 或 AOF 文件的加载。Redis 会自动根据配置文件和现有的持久化文件(RDB 和 AOF)进行数据恢复。

手动干预情况

尽管 Redis 会自动处理持久化文件的加载,但在某些特殊情况下可能需要手动干预:

  1. 文件损坏:如果 RDB 或 AOF 文件被损坏,Redis 无法正常加载数据,可能需要手动修复或选择备份文件进行恢复。
  2. 切换持久化策略:如果你想在恢复过程中切换持久化策略,比如从 AOF 切换到 RDB 或反之,可能需要手动调整配置文件并重启 Redis。
  3. 灾难恢复:在极端情况下,如数据中心级别的故障,可能需要手动从备份中恢复 RDB 或 AOF 文件,然后启动 Redis。

如果确实需要手动恢复,可以按照以下步骤进行操作:

步骤一:停止 Redis 实例

为了确保数据恢复过程中的一致性和安全性,建议首先停止正在运行的 Redis 实例。在终端中执行以下命令:

redis-cli shutdown

或者,如果 Redis 是作为服务运行的,可以使用以下命令:

sudo service redis-server stop

步骤二:备份现有 RDB 文件

在进行数据恢复之前,最好先备份当前的 RDB 文件,以防止恢复失败造成的数据丢失。假设原始 RDB 文件路径为 /var/lib/redis/dump.rdb

mv /var/lib/redis/dump.rdb /var/lib/redis/dump.rdb.bak

步骤三:替换 RDB 文件

将你的备份 RDB 文件复制到 Redis 数据目录,并重命名为 dump.rdb。例如,如果备份文件在 /path/to/backup 目录下:

cp /path/to/backup/dump-yyyyMMdd_HHmmss.rdb /var/lib/redis/dump.rdb

步骤四:启动 Redis 实例

重新启动 Redis 实例以加载新的 RDB 文件。在终端中执行以下命令:

redis-server /etc/redis/redis.conf

或者,如果 Redis 是作为服务运行的,可以使用以下命令:

sudo service redis-server start

步骤五:验证数据恢复

启动 Redis 后,通过 redis-cli 或其他客户端连接到 Redis,检查关键数据是否已正确恢复。

redis-cli

在进入 Redis 命令行后,可以通过一些基本命令来验证数据,例如:

keys *
get some_key

注意事项

  1. 数据一致性:在恢复过程中,确保不对 Redis 实例进行写操作,以避免数据不一致。
  2. 定期备份:养成定期备份的习惯,以便在需要时随时可以进行数据恢复。
  3. 多版本备份:保留多个历史版本的备份文件,以应对不同时间点的数据恢复需求。
  4. 监控与告警:设置监控和告警系统,及时发现备份和恢复中的问题。

自动化恢复脚本示例

为了简化数据恢复流程,可以编写一个简单的自动化脚本:

#!/bin/bash

# 停止 Redis 服务
sudo service redis-server stop

# 备份现有 RDB 文件
mv /var/lib/redis/dump.rdb /var/lib/redis/dump.rdb.bak

# 替换 RDB 文件
cp /path/to/backup/dump-yyyyMMdd_HHmmss.rdb /var/lib/redis/dump.rdb

# 启动 Redis 服务
sudo service redis-server start

# 输出恢复结果
if [ $? -eq 0 ]; then
    echo "Redis 数据恢复成功"
else
    echo "Redis 数据恢复失败"
fi

保存此脚本为 restore.sh,并通过以下命令给予执行权限:

chmod +x restore.sh

然后执行该脚本:

./restore.sh

AOF写入与恢复流程

Redis的AOF(Append Only File)机制是一种记录每个写操作日志的持久化方法,相比RDB更能确保数据的实时性和完整性。下面详细介绍AOF的写入与恢复流程。

AOF写入流程

  1. 命令追加

    • 每当客户端发送一个写操作(如SET、HSET等)时,Redis会将该命令追加到AOF缓冲区中。
    • 这是通过内存中的一种线性方式进行的,以保证写操作是顺序的。
  2. AOF缓冲区同步到硬盘

    • Redis配置文件redis.conf中有三个选项控制AOF文件的同步策略:

      appendfsync always      # 每次写操作后都立即同步到硬盘,性能较慢但最安全。
      appendfsync everysec    # 每秒同步一次,这是默认值,折衷了性能和安全性的需求。
      appendfsync no          # 让操作系统决定何时同步,这种模式下性能最好,但风险最大。
      
    • 根据配置选择不同的同步策略,Redis会将AOF缓冲区中的数据写入磁盘。

  3. AOF重写(Rewrite)

    • 随着时间的推移,AOF文件可能会变得非常大,因此需要定期对其进行压缩以减少文件大小。
    • 重写操作由BGREWRITEAOF命令触发,或根据配置自动进行。
    • Redis创建一个子进程,将当前数据库的状态保存成一个新的AOF文件。与此同时,主进程继续处理新的写操作,并将这些操作记录到一个额外的缓冲区中。
    • 当子进程完成新AOF文件的创建后,Redis将缓冲区中的新写操作也写入到新的AOF文件中,最后用新的AOF文件替换旧文件。

AOF恢复流程

当Redis启动时,如果启用了AOF持久化,会通过以下步骤从AOF文件恢复数据:

  1. 读取AOF文件

    • Redis启动时,会首先检查是否存在AOF文件(通常路径为/var/lib/redis/appendonly.aof)。
  2. 逐条执行命令

    • Redis按顺序读取AOF文件中的每一条写操作命令,并在内存中重新执行这些命令以重建数据库的状态。
  3. 数据一致性校验

    • Redis会对AOF文件进行基本的一致性校验,确保文件中的命令没有损坏或不完整。
    • 如果发现异常,如AOF文件末尾出现不完整的命令,Redis会尝试修复(截断文件到最后一个完整命令),并给出相应的告警。

小结

通过合理配置AOF的同步策略,能够在性能和数据安全之间找到平衡点。结合AOF重写机制,可以有效管理AOF文件的大小,防止其无限增长。下面是详细的AOF写入和恢复流程示例以及相关的脚本。

示例:AOF配置及命令

# 在 redis.conf 文件中配置 AOF 相关参数
appendonly yes                # 启用AOF
appendfilename "appendonly.aof" # 设置AOF文件名称
appendfsync everysec          # 每秒将AOF缓冲区的内容同步到硬盘

示例:触发AOF重写

手动触发AOF重写,可以使用BGREWRITEAOF命令:

redis-cli BGREWRITEAOF

自动化恢复脚本

如果确实需要手动恢复,可以编写一个简单的自动化脚本:

#!/bin/bash

# 停止 Redis 服务
sudo service redis-server stop

# 备份现有的AOF文件
mv /var/lib/redis/appendonly.aof /var/lib/redis/appendonly.aof.bak

# 替换为你的备份文件
cp /path/to/backup/appendonly-yyyyMMdd_HHmmss.aof /var/lib/redis/appendonly.aof

# 启动 Redis 服务
sudo service redis-server start

# 验证数据恢复
redis-cli ping
if [ $? -eq 0 ]; then
    echo "AOF 数据恢复成功"
else
    echo "AOF 数据恢复失败"
fi

保存此脚本为 restore_aof.sh,并通过以下命令给予执行权限

  1. chmod +x restore_aof.sh
    
  2. 执行脚本:

    bash复制代码
    ./restore_aof.sh
    

注意事项

  • 在执行脚本之前,请确保已停止所有客户端对Redis实例的写操作,以避免数据不一致。
  • 定期备份AOF文件,并将备份文件安全存储在异地或云端,以防止单点故障导致的数据丢失。
  • 根据业务需求调整AOF同步策略(appendfsync),以平衡性能和数据安全性。

AOF什么场景会触发重写

Redis 的 AOF(Append Only File)机制提供了数据持久化的高安全性,但随着时间的推移,AOF 文件会不断增长,这可能导致磁盘空间的浪费和加载时间的延长。为了优化 AOF 文件的大小和性能,Redis 提供了 AOF 重写功能。

触发 AOF 重写的场景

  1. 手动触发

    • 可以通过命令 BGREWRITEAOF 手动触发 AOF 重写。
    • 命令格式:BGREWRITEAOF
  2. 自动触发

    • Redis 配置文件(redis.conf)中可以设置自动触发 AOF 重写的条件,主要包括以下两个配置项:

      • auto-aof-rewrite-percentage: 当当前 AOF 文件大小相对于上一次重写后的大小增加多少百分比时触发重写。

      • auto-aof-rewrite-min-size: 设置触发重写的最小 AOF 文件大小。

      • 示例配置:

        auto-aof-rewrite-percentage 100
        auto-aof-rewrite-min-size 64mb
        

自动重写示例

假设你设置了 auto-aof-rewrite-percentage 为 100 和 auto-aof-rewrite-min-size 为 64MB:

  • 当 AOF 文件首次达到 64MB 时,会尝试进行重写。
  • 如果重写后 AOF 文件的大小为 32MB,那么当 AOF 文件再次增长到 64MB(即增加了 100%)时,又会触发一次重写。

重写过程

  1. 生成新的 AOF 文件

    • Redis Fork(分叉)一个子进程来执行重写工作。
    • 子进程将当前内存中的所有数据以最紧凑的方式写入一个新的 AOF 文件,不包括任何已经过期的键或多余的操作。
  2. 合并操作

    • 新的 AOF 文件只包含设置每个键值对的必要命令,而不会重复之前的操作,从而显著减少文件大小。
  3. 替换旧的 AOF 文件

    • 新文件生成完成后,Redis 会把新文件替换旧的 AOF 文件。

优点

  • 压缩文件大小:通过重写,可以大幅减小 AOF 文件的大小。
  • 提高恢复速度:较小的 AOF 文件意味着在服务器启动时,数据恢复的速度更快。
  • 系统负载低:重写操作由子进程执行,不会阻塞主进程,保证了系统的高可用性。

通过合理配置和使用 AOF 重写功能,可以有效地平衡数据安全性和系统性能。

思考题1:RDB和AOF如何配合,才能最大程度的保证数据的一致性

配置redis.conf

在 Redis 配置文件中,同时启用 RDB 和 AOF:

# RDB 配置
save 900 1    # 在 900 秒内,如果至少有 1 个键发生变化,则触发一次快照
save 300 10   # 在 300 秒内,如果至少有 10 个键发生变化,则触发一次快照
save 60 10000 # 在 60 秒内,如果至少有 10000 个键发生变化,则触发一次快照

dbfilename dump.rdb
dir /var/lib/redis

# AOF 配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec

# 开启AOF重写时的RDB保存,以便重新生成的AOF文件拥有更一致的数据集
aof-use-rdb-preamble yes

启用混合持久化

从 Redis 4.0 开始,Redis 引入了 aof-use-rdb-preamble 选项,这一选项允许 AOF 文件的前半部分以 RDB 格式保存,从而加速恢复过程并减少文件大小。

aof-use-rdb-preamble yes

此配置使得在重写 AOF 文件时,Redis 会首先将当前数据库状态保存为 RDB 格式,然后继续记录增量的写操作命令。这样在恢复时,可以先加载 RDB 部分,再重放 AOF 部分,大大提高了恢复速度,同时保持了高一致性。

恢复流程

当 Redis 同时使用 RDB 和 AOF 时,恢复流程如下:

  1. 加载 RDB 文件:如果存在 RDB 文件,Redis 首先会加载它,恢复到最近一次生成快照时的数据状态。
  2. 重放 AOF 文件:随后,Redis 将重放 AOF 文件中的所有写操作日志,以确保包括 RDB 快照后发生的所有数据变更。

这个流程确保了即使在最近一次 RDB 快照之后发生了崩溃,仍然能通过 AOF 日志恢复所有未写入 RDB 的数据变化,从而最大程度上保证数据的一致性。

优化建议

为了进一步优化和保障数据一致性,可以考虑以下措施:

  1. 适当调整 AOF 的同步策略

    • always: 每次写操作后立即同步到磁盘,这样可以最大限度保证数据持久性,但对性能影响较大。
    • everysec: 每秒同步一次,这是一个折衷方案,能够较好地平衡性能和数据安全。
    • no: 不主动同步,由操作系统决定何时写入磁盘,性能最好但风险最高。

    推荐使用 everysec,这也是默认值,在性能和数据安全之间取得平衡。

  2. 定期手动触发 RDB 保存:尽管 Redis 会根据配置自动触发 RDB 保存,但在一些关键业务操作后,可以手动触发 BGSAVE 命令,确保重要数据及时持久化。

  3. 监控和告警:设置 Redis 的监控和告警,及时发现和处理潜在问题,防止数据丢失或持久化失败。

  4. 定期备份:周期性地备份 RDB 文件和 AOF 文件,并将其存储在异地或云端,增强灾难恢复能力。