Redis 持久化
Redis 持久化
1. 持久化的意义
Redis
是内存数据库,其将自己的数据存储在内存中,如果 Redis 发生宕机,且没有进行持久化的,那么Redis
重启后将没有之前的数据。而通过持久化,Redis
可以在重启后,快速找回之前的数据,防止大量请求打入数据库。
Redis
持久化的方式有两种:RDB
持久化、AOF
持久化。
2. RDB 持久化
RDB 持久化便是生成一个 RDB 文件,该文件是一个经过压缩的二进制文件。
2.1 RDB 文件的创建与载入
Redis
有两个命令用于生成 RDB
文件,一个是 SAVE
,另一个是 BGSAVE
。
SAVE`` 命令会阻塞
Redis 服务器进程,直到
RDB `文件创建完毕,期间不能处理任何客户端的命令。
BGSAVE
命令则会创建一个子进程,由子进程负责创建 RDB
文件,服务器进程依然可以继续处理命令请求。
RDB
文件的载入工作是在服务器启动时自动执行的,所以Redis
并没有专门用于载入RDB
文件的命令,只要Redis
服务器在启动时检测到RDB
文件存在,它就会自动载入RDB
文件。
因为AOF
文件的更新频率通常比RDB文件的更新频率高,所以:
- 如果服务器开启了
AOF
持久化功能,那么服务器会优先使用AOF
文件来还原数据库状态。 - 只有在
AOF
持久化功能处于关闭状态时,服务器才会使用RDB
文件来还原数据库状态。
服务器在载入RDB
文件期间,会一直处于阻塞状态,直到载入工作完成为止。
2.2 自动间隔性保存
Redis
允许用户通过设置服务器配置的 save
选项,让服务器每隔一段时间自动执行一次BGSAVE
命令。
用户可以通过save
选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE
命令。
服务器程序会根据 save
选项所设置的保存条件,设置服务器状态 redisServer
结构的 saveparams
属性:
struct redisServer{
// ...
// 记录了保存条件的数组
struct saveparam *saveparam;
// ...
};
struct saveparam{
// 秒数
time_t seconds;
// 修改数
int changes;
}
如果 save
选项的值同上,那么服务器状态中的 saveparams
数组如下图所示:
服务器状态还维持了一个 dirty
计数器,以及一个lastsave
属性:
-
dirty
计数器记录了距离上一次成功执行SAVE
命令或者BGSAVE
命令之后,服务器对数据库状态(所有的数据)进行了多少次修改。 -
lastsave
属性记录了上一次成功执行SAVE
命令或BGSAVE
命令的时间。
struct redisServer{
// ...
// 修改计数器
long long dirth;
// 上一次执行保存的时间
time_t lastsave;
}
Redis
的服务器周期性操作函数 serverCron
默认每隔100
毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save
选项所设置的保存条件是否已经满足,如果满足的话,就执行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 ()
# ...
3. AOF 持久化
除了RDB
持久化功能之外,Redis
还提供了AOF
( Append Only File
)持久化功能。
RDB
持久化通过保存数据库中的所有键值对,生成当前时刻的快照文件来记录数据库状态不同。
AOF
持久化是通过保存Redis
服务器所执行的写命令来记录数据库状态的。
3.1 AOF 持久化的实现
AOF 持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
3.1.1 命令追加
服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf
缓冲区的末尾。(注意,这里并没有直接写入到AOF
文件中)
每当有新的写入命令时,都会将命令先写入到aof_buf
缓冲区内。
3.1.2 AOF 文件的写入和同步
服务器每次结束一个文件事件循环之前,都可能会执行写命令,使得一些内容被追加到aof_buf
缓冲区里面,所以每次结束一个事件循环之前, 它都会调用 flushAppend0n1yFile
函数,考虑是否需要将aof_ buf
缓冲区中的内容写人和保存到AOF
文件里面,这个过程可以用以下伪代码表示:
def eventLoop():
while True:
# 处理文件事件,接收命令请求以及发送命令回复
# 处理命令请求时可能会有新内容被追加到 aof_buf 缓冲区中
processFileEvents()
# 处理时间事件
processTimeEvents()
# 考虑是否将 aof_buf 中的内容写入和保存的 AOF 文件里面
flushAppendOblyFile()
flushAppendOblyFile
函数的是否进行写入AOF
文件则是由服务器配置的 appendfsync
选项的值来决定。
即使调用flushAppendOblyFile()
函数,也不意味aof_buf
缓冲区中的内容一定被写入到文件中,因为Redis
和磁盘之间还有一层OS
,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,当缓冲区的空间被填满,或者超过了指定的时间后,才真正将缓冲区中的数据写入到磁盘里面。
这样便可能导致数据被操作系统写入到了内存缓冲区,但是OS
还未来的及落地到磁盘,造成数据丢失。
always
则代表每执行一条写命令就落地到磁盘,但是Redis
的效率会大幅降低。
everysec
则代表每隔一秒就落地到磁盘,该情况通常只会丢失掉一秒的数据。
no
则是由操作系统决定何时落地到磁盘,效率最高,如果宕机,则可能会丢失掉上次的同步到宕机时间内的数据。
3.2 AOF 文件的载入与数据还原
3.3 AOF 重写
随着服务器运行时间的流逝,AOF
文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF
文件很可能对Redis
服务器、甚至整个宿主计算机造成影响,并且AOF
文件的体积越大,使用AOF
文件来进行数据还原所需的时间就越多。
为了解决AOF
文件体积膨胀的问题,Redis
提供了AOF
文件重写( rewrite
)功能。
通过该功能,Redis
服务器可以创建一个新的AOF
文件来替代现有的AOF
文件,新旧两个AOF
文件所保存的数据库状态相同,但新AOF
文件不会包含任何浪费空间的冗余命令,所以新AOF
文件的体积通常会比旧AOF
文件的体积要小得多。
3.3.1 AOF 重写的实现
新的AOF
文件并不是基于之前的AOF
文件进行分析和重写的,因为这样的效率并没有直接扫描Redis
中的键值对的速度快。
首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前这个键值对的多条命令,这就是AOF
重写功能的实现原理。
整个重写过程可以用以下伪代码表示:
def aof_rewrite (new_aof_file_name) :
# 创建新AOF文件
f = create_file (new_aof_file_name)
# 遍历数据库
for db in redisServer.db:
# 忽略空数据库
if db.is_empty() : continue
# 写入SELECT 命令,指定数据库号码
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)
elif key.type == Hash:
rewrite hash (key)
elif key.type == Set:
rewrite_set (key)
elif key.type == SortedSet :
rewrite_sorted_set (key)
# 如果键带有过期时间,那么过期时间也要被重写
if key.have_expire_time() :
rewrite_expire_time (key)
# 写入完毕,关闭文件
f.close ()
def rewrite_string (key) :
# 使用GET命令获取字符串键的值
value = GET (key)
# 使用SET命令重写字符串键
f.write_command (SET, key, value)
def rewrite_list (key) :
# 使用LRANGE命令获取列表键包含的所有元素
iteml,item2, .... itemN = LRANGE (key, 0, -1)
# 使用RPUSH命令重写列表键
f.write_command (RPUSH, key, iteml, item2, .... itemN)
def rewrite_hash (key) :
# 使用HGETALL命令获取哈希键包含的所有键值对
fieldl, valuel, field2, value2, .... fieldN, valueN = HGETALL (key)
# 使用HMSET命令重写哈希键
f.write_command (HMSET,key, field1, value1, field2, value2, ....,fieldN,valueN)
def rewrite_set (key) :
# 使用SMEMBERS命令获取集合键包含的所有元素
elem1, elem2,elemN = SMEMBERS (key)
# 使用SADD命令重写集合键
f.write_command (SADD, key, elem1, elem2, .... elemN)
def rewrite_sorted_set (key) :
# 使用ZRANGE命令获取有序集合键包含的所有元素
member1,scorel, member2 ,score2,...,memberN, scoreN
= ZRANGE (key, 0,-1,"WITHSCORES")
# 使用ZADD命令重写有序集合键
f. write_ command (ZADD, key, scorel, member1, score2, member2,scoreN,memberN)
def rewrite_expire_time (key) :
# 获取毫秒精度的键过期时间戳
timestamp = get_ expire_ time_ in_ unixstamp (key)
# 使用PEXPIREAT命令重写键的过期时间
f.write_command (PEXPIREAT, key, timestamp)
3.3.2 AOF 后台重写
AOF
重写为了避免造成阻塞,其是通过一个子进程来完成的。
同时,为了防止AOF
子进程重写的过程中,客户端又有新的写命令,造成数据不一致的情况。Redis
服务器设置了一个AOF
重写缓冲区,该缓冲区在服务器创建子进程后开始使用。当Redis
服务器执行完一个写命令之后, 它会同时将这个写命令发送给AOF
缓冲区和AOF
重写缓冲区。
AOF
重写完成后,子进程会给父进程发送一个信号,父进程便会执行如下操作:
- 将
AOF
重写缓冲区中所有内容写入到新AOF
文件中,此时文件所保存的内容便和服务器数据一致了。 - 对新
AOF
文件进行改名,原子地覆盖现有AOF
文件,完成新旧两个文件的替换。
4. 两种持久化方式的优点和缺点
4.1 RDB 持久化机制的优点和缺点
优点
-
RDB
会生成多个数据文件,每个数据文件都代表了某一个时刻中Redis
的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份Redis
中的数据。 -
RDB
对Redis
对外提供的读写服务,影响非常小,可以让Redis
保持高性能,因为Redis
主进程只需要fork
一个子进程,让子进程执行磁盘IO
操作来进行RDB
持久化即可。 -
相对于
AOF
持久化机制来说,直接基于RDB
数据文件来重启和恢复Redis
进程,更加快速。
缺点
- 如果想要在
Redis
故障时,尽可能少的丢失数据,那么RDB
没有AOF
好。一般来说,RDB
数据快照文件,都是每隔5
分钟,或者更长时间生成一次,这个时候就得接受一旦Redis
进程宕机,那么会丢失最近5
分钟的数据。 -
RDB
每次在fork
子进程来执行RDB
快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
4.2 AOF 持久化机制的优点和缺点
优点
-
AOF
可以更好的保护数据不丢失,一般AOF
会每隔1秒,通过一个后台线程执行一次fsync
操作,最多丢失1
秒钟的数据。 -
AOF
日志文件以append-only
模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。 -
AOF
日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite
的时候,通过子进程创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge
后的日志文件ready
的时候,再交换新老日志文件即可。 -
AOF
日志文件的命令通过可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall
命令清空了所有数据,只要这个时候后台rewrite
还没有发生,那么就可以立即拷贝AOF
文件,将最后一条flushall
命令给删了,然后再将该AOF
文件放回去,就可以通过恢复机制,自动恢复所有数据。
缺点
- 对于同一份数据来说,
AOF
日志文件通常比RDB
数据快照文件更大。 -
AOF
开启后,支持的写QPS
会比RDB
支持的写QPS
低,因为AOF
一般会配置成每秒fsync
一次日志文件,当然,每秒一次fsync
,性能也还是很高的。
5. RDB 和 AOF 到底该如何选择
-
不要仅仅使用
RDB
,因为那样会导致你丢失很多数据 -
也不要仅仅使用
AOF
,因为那样有两个问题,第一,你通过AOF
做冷备,没有RDB
做冷备,来的恢复速度更快; 第二,RDB
每次简单粗暴生成数据快照,更加健壮,可以避免AOF
这种复杂的备份和恢复机制的bug
-
综合使用
AOF
和RDB
两种持久化机制,用AOF
来保证数据不丢失,作为数据恢复的第一选择; 用RDB
来做不同程度的冷备,在AOF
文件都丢失或损坏不可用的时候,还可以使用RDB
来进行快速的数据恢复。