Redis持久化
Redis 使用操作系统的 写时复制技术 COW(Copy On Write) 来实现快照的持久化。fork()让父子进程共享内存,从而减少内存占用,并且避免了没有必要的数据复制。
fork函数有两点注意:
fork 的这个过程主进程是阻塞的,fork完之后不阻塞。
当数据集比较大的时候,fork的过程是非常耗时的,过程可能会持续数秒,可能会因为数据量大而导致主进程长时间被挂起,造成Redis服务不可用。
redis有RDB和AOF两种持久化方式。RDB是快照文件的方式,redis通过执行SAVE/BGSAVE命令,执行数据的备份,将redis当前的数据保存到*.rdb文件中,文件保存了所有的数据集合。AOF是服务器通过读取配置,在指定的时间里,追加redis写操作的命令到*.aof文件中,是一种增量的持久化方式。
(1)RDB方式:
SAVE命令会阻塞Redis服务进程,直到RDB文件创建完成为止。
BGSAVE命令通过fork子进程,有子进程来进行创建RDB文件,父进程和子进程共享数据段,父进程继续提供读写服务,子进程实现备份功能。BGSAVE阶段只有在需要修改共享数据段的时候才进行拷贝,也就是COW(Copy On Write)。有了RDB文件之后,如果服务器关机了,或者需要新增一个服务器,重新启动数据库服务器之后,就可以通过载入RDB文件恢复之前备份的数据。
但是bgsave会耗费较长时间,不够实时,会导致在停机的时候丢失大量数据。
RDB常见配置:
save 900 1
save 300 10
save 60 10000
#从 Redis 最近一次创建快照算起,当 “60 秒内有 10000 次写入” 这个条件被满足时, Redis 就会自动触发 BGSAVE 命令
rdbcompression yes 压缩
stop-writes-on-bgsave-error yes 当bgsave失败停止写入。
rdbchecksum yes 校验
dbfilename dump.rdb RDB备份文件
dir /data/redis/6379
replica-serve-stale-data:版本Redis 4.0 以上,指定复制节点在与主节点断开连接后是否继续向客户端提旧数据。当设置为 yes 时,在复制节点与主节点断开连接后,该节点将继续向客户端提供旧数据,直到重新连接上主节点并且同步完全新的数据为止;当设置为 no 时,复制节点会立即停止向客户端提供数据,并且等待重新连接上主节点并同步数据。需要注意的是,当 replica-serve-stale-data 设置为 yes 时,可能会存在一定的数据不一致性问题,因此建议仅在特定场景下使用。
repl-diskless-sync:版本Redis 2.8 以上,用于指定复制节点在进行初次全量同步(即从主节点获取全部数据)时是否采用无盘同步方式。 yes 时,master不会将数据存储到本地磁盘,然后发给slave;当设置为 no 时,复制节点将先将主节点的数据保存到本地磁盘中,然后再进行同步操作。采用无盘同步方式可以避免磁盘 IO 操作对系统性能的影响,但同时也会增加网络负载和内存占用。
1)当 Redis 通过 SHUTDOWN 命令接收到关闭服务器的请求时,或者接收到 TERM命令时,会执行一个 SAVE 命令,并且阻塞所有的客户端,不再执行任何请求。在 SAVE 命令执行结束后关闭服务器。
2)当一个 Redis 服务器连接另一个 Redis 服务器,并向对方发送 SYNC 命令来开始一次复制操作的时候,如果主服务器没有在执行 BGSAVE 操作,或者主服务器并非刚执行完 BGSAVE,那么主服务器会执行 BGSAVE 命令。
(2)AOF(Append Only File)
RDB文件保存的是数据库的键值对数据,AOF保存的是数据库执行的写命令。
举个例子,如果你不小心执行了 FLUSHALL 命令,导致数据被误删了 ,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL 执行之前的状态。
常见配置:
appendonly no 打开aof
appendfilename appendonly.aof
appendfsync everysec #每秒 always总是 no关闭
no-appendfsync-on-rewrite no 阻塞写操作直到aof重写完成。
auto-aof-rewrite-percentage 100 文件大小增长比超过这个值开始自动重写 aof
auto-aof-rewrite-min-size 64mb 文件大小超过这个值才可以有可能自动重写 aof
aof-load-truncated no 指redis在恢复时,会忽略最后一条可能存在问题的指令。
aof-rewrite-incremental-fsync yes 子进程根据内存快照,按照命令合并规则写入到新AOF文件。控制每次批量写入硬盘数据量默认是32MB,防止单词刷盘数据过多造成硬盘阻塞
auto-aof-rewrite-percentage(100)和 auto-aof-rewrite-min-size(64M):这两个配置项用于设置 AOF 重写规则。当 AOF 文件大小超过 auto-aof-rewrite-min-size 设置的值,并且 AOF 文件增长率达到
auto-aof-rewrite-percentage 所定义的百分比时,Redis 会启动 AOF 重写操作。
aof-use-rdb-preamble:混合持久化。如果设置为yes,在AOF文件头加入一个RDB文件的内容,可以尽可能的减小AOF文件大小,同时也方便恢复数据。
AOF的持久化发生时期有个配置选项:appendfsync。该选项有三个值:
always:所有内容写入并同步到aof文件
everysec:将aof_buf缓冲区的内容写入到AOF文件,如果距离上次同步AOF文件的超过设置时间,进行同步到文件。
no:将aof_buf缓冲区中的所有内容写入到AOF文件,但并不对AOF文件进行同步,由操作系统决定何时进行同步,一般是默认情况下的30s。
AOF持久化模式每个写命令都会追加到AOF文件,随着服务器不断运行,AOF文件会越来越大,为了避免AOF产生的文件太大,服务器会对AOF文件进行重写,将操作相同key的相同命令合并,从而减少文件的大小。
AOF的重写会执行大量的写入操作,Redis是单线程的,所以如果有服务器直接调用重写,服务器就不能处理其他命令了,因此Redis服务器新起了单独一个进程来执行AOF重写。等到子进程完成了重写工作后,会发一个完成的信号给服务器,服务器就将AOF重写缓冲区中的所有内容追加到AOF文件中,然后原子性地覆盖现有的AOF文件。
(3)AOF 重写
AOF 文件会越来越大,达到几G甚至几十个G,对服务器可能造成影响,使用 AOF 来进行数据恢复所需的时间也就越多。为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写(rewrite)机制。
重写机制指的是将 AOF 文件中的冗余命令删除,例如:
我对 Redis 执行了下面六条命令:
rpush list "A"
rpush list "B"
rpush list "C"
rpush list "D"
rpush list "E"
rpush list "F"
AOF 进行重写一条命令:rpush list "A" "B" "C" "D" "E" "F"
手动触发AOF重写的命令 BGREWRITEAOF,使用INFO PERSISTENCE 命令查看当前AOF文件的大小和重写过程的状态。
(4)AOF 重写缓冲区
子进程在AOF重写期间,父进程还是在继续接收和处理命令的。新的命令可能会对现有的数据库状态进行修改,从而使得当前数据和重写后的AOF文件所保存的数据不一致。为了解决这个问题,在重写开始时,新产生一个重写缓存区,用于记录重写期间的数据变更。
那么AOF缓存区也是变更数据,是否可以使用AOF替代AOF重写区呢?
不可以,AOF是记录的重写开始后的所有变更。而AOF缓冲区则是当前未落盘的变更,已经落盘的就会清理掉了。他不是一个完整的记录。
(5)AOF 写后日志
我们比较熟悉的是数据库的写前日志(Write Ahead Log,WAL),也就是说,在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。Innodb 存储引擎中的 redo log(重做日志)。
AOF 日志却正好相反,它是写后日志。
思考:为什么要这样设计?
(1)避免额外的检查开销,先让系统执行命令,只有命令能执行成功,才会被记录到日志中。并不会先去对这些命令进行语法检查。否则日志中就有可能记录了错误的命令,就无法使用其恢复。
(2)在命令执行后才记录日志,所以并不会阻塞当前的写操作。
不过,写后日志也有两个潜在的风险:
(1)如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
(2)后写日志虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也会阻塞。
(6)RDB和AOF的优缺点
RDB持久化方式可以只通过服务器读取数据就能加载备份中的文件到程序中,而AOF方式必须创建一个伪客户端才能执行。
RDB的文件较小,保存了某个时间点之前的数据,适合做灾备和主从同步。
RDB备份耗时较长,如果数据量大,在遇到宕机的情况下,可能会丢失部分数据。另外,RDB是通过配置使达到某种条件的时候才执行,如果在这段时间内宕机,那么这部分数据也会丢失。
AOF方式,在相同数据集的情况下,文件大小会比RDB方式的大。
AOF的持久化方式也是通过配置的不同,默认配置的是每秒同步,最快的模式是同步每一个命令,最坏的方式是等待系统执行fsync将缓冲同步到磁盘文件中,大部分操作系统是30s。通常情况下会配置为每秒同步一次,所以最多会有1s的数据丢失。
RDB和AOF方式结合。起一个定时任务,每小时备份一份服务器当前状态的数据,以日期和小时命名,另外起一个定时任务,定时删除无效的备份文件(比如48小时之前)。AOF配置为1s一次。这样一来,最多会丢失1s的数据,同时如果redis发生雪崩,也能迅速恢复为前一天的状态,不至于停止服务。
(7)解析RDB
打印出16进制格式的参数是x,字符是c,将字符与十六进制的对应关系打印出来:od -A x -t x1c -v dump.rdb
文件打印出来的都是一些十六进制的数字,转换成十进制再去ASCII码表就能查找到对应的字符。比如第一个字符,52=5*16+2*1=82='R'。
(8)RDB或AOF损坏,修复
因为redis启动会先读取aof,然后读取rdb,如果文件均损坏,则redis会报错,无法启动。
redis-check-aof 工具修复aof文件。
redis-check-rdb 工具来修复dump.rdb
(9)混合持久化
开启混合持久化之后,AOF文件中的内容:前半部分是二进制的RDB内容,后面跟着AOF增加的数据,AOF位于两次RDB之间。
开启:
appendonly yes
aof-use-rdb-preamble yes
当你想选择适合你的应用程序的持久化方式时,你需要考虑以下两个因素:
如果对数据的实时性和一致性有很高的要求,则AOF可能是更好的选择。如果希望能快速地加载数据并减少磁盘空间,那么RDB可能更适合你的应用程序。RDB文件是二进制格式的,结构非常紧凑。如果对Redis的性能有很高的要求,那么关闭持久化功能也是一个选择。因为持久化功能可能会影响Redis的性能,但是一般不建议这么做。
(10)过期key对持久化的影响
RDB 的主要问题是,如果系统发生崩溃,那么最近一次执行完快照后修改的数据将被丢失。因此,RDB 适合用于即使丢失一部分数据也不会造成影响的应用程序。
AOF 配置ererysec 是一种兼顾性能与数据安全的方式,在这种情况下,如果系统崩溃,用户最多会丢失一秒内的数据。
AOF 的缺点是随着 Redis 的不断运行,AOF 文件可能会非常大,甚至用完硬盘的空间。解决这个问题的办法是 AOF 重写。
1、在生成 RDB 文件的过程中,如果一个键已经过期,那么其不会被保存到 RDB 文件中。在载入 RDB 的时候,要分两种情况:
如果 Redis 以主服务器的模式运行,那么会对 RDB 中的键进行时间检查,过期的键不会被恢复到 Redis 中。
如果 Redis 以从服务器的模式运行,那么 RDB 中所有的键都会被载入,忽略时间检查。在从服务器与主服务器进行数据同步的时候,从服务器的数据会先被清空,所以载入过期键不会有问题。
2、对于 AOF 来说,如果一个键过期了,那么不会立刻对 AOF 文件造成影响。因为 Redis 使用的是惰性删除和定期删除,只有这个键被删除了,才会往 AOF 文件中追加一条 DEL 命令。在重写 AOF 的过程中,程序会检查数据库中的键,已经过期的键不会被保存到 AOF 文件中。
3、在运行过程中,对于主从复制的 Redis,主服务器和从服务器对于过期键的处理也不相同:
对于master服务器,一个过期的键被删除了后,会向从服务器发送 DEL 命令,通知从服务器删除对应的键
slave服务器接收到读取一个键的命令时,即使这个键已经过期,也不会删除,而是照常处理这个命令。
slave服务器接收到主服务器的 DEL 命令后,才会删除对应的过期键。
这么做的主要目的是保证数据一致性,所以当一个过期键存在于主服务器时,也必然存在于从服务器。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构