赞
踩
在此先贴上AOF和RDB优先级处理流程:
因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态
Redis读取AOF文件 并 还原数据库状态的详细步骤如下:
当完成以上步骤之后,AOF文件所保存的数据库状态就会被完整地还原出来,整个过程如下所示:
注意以下几点:
加载损坏的AOF文件时会拒绝启动,并打印如下日志:
# Bad file format reading the append only file: make a backup of your AOF file,
then use ./redis-check-aof --fix <filename>
对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof--fix
命令进行修复,修复后使用diff -u
对比数据的差异,找出丢失的数据,有些可以人工修改补全
AOF文件可能存在结尾不完整的情况,比如机器突然掉电 导致AOF尾部文件命令写入不全
Redis为我们提供了aof-load-truncated
配置来兼容这种情况,默认开启
加载AOF时,当遇到此问题时会忽略并继续启动,同时打印如下警告日志:
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 397856725 !!!
# AOF loaded anyway because aof-load-truncated is enabled
因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF文件很可能对Redis服务器、甚至整个宿主计算机造成影响,并且AOF文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多
为了解决AOF文件体积膨胀的问题,Redis提供了 AOF文件 重写(rewrite)功能
通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但 新AOF文件 不会包含 任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多
首先 从数据库中 读取 键现在的值,然后 用一条命令 去记录 键值对,代替 之前记录这个键值对的多条命令,这就是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命令获取列表键包含的所有元素
item1, item2, ..., itemN = LRANGE(key, 0, -1)
# 使用RPUSH命令重写列表键
f.write_command(RPUSH, key, item1, item2, ..., itemN)
def rewrite_hash(key):
# 使用HGETALL命令获取哈希键包含的所有键值对
field1, value1, 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, score1, member2, score2, ..., memberN, scoreN = ZRANGE(key, 0, -1, "WITHSCORES")
# 使用ZADD命令重写有序集合键
f.write_command(ZADD, key, score1, member1, score2, member2, ..., scoreN, memberN)
def rewrite_expire_time(key):
# 获取毫秒精度的键过期时间戳
timestamp = get_expire_time_in_unixstamp(key)
# 使用PEXPIREAT命令重写键的过期时间
f.write_command(PEXPIREAT, key, timestamp)
因为aof_rewrite
函数生成的新AOF文件 只包含 还原当前数据库状态所必须的命令,所以新AOF文件不会浪费任何硬盘空间
注意:在实际中,为了避免 在执行命令时 造成 客户端输入缓冲区溢出,重写程序 在处理 列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,如果元素的数量超过了redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD
常量的值,那么 重写程序 将使用 多条命令来记录键的值,而不单单使用一条命令
在目前版本中,REDIS_AOF_REWRITE_ITEMS_PER_CMD
常量的值为64,这也就是说,如果一个集合键包含了超过64个元素,那么重写程序会用多条SADD
命令来记录这个集合,并且每条命令设置的元素数量也为64个:
SADD <set-key> <elem1> <elem2> ... <elem64>
SADD <set-key> <elem65> <elem66> ... <elem128>
SADD <set-key> <elem129> <elem130> ... <elem192>
如果一个列表键包含了超过64个项,那么重写程序会用多条RPUSH
命令来保存这个列表,并且每条命令设置的项数量也为64个:
RPUSH <list-key> <item1> <item2> ... <item64>
RPUSH <list-key> <item65> <item66> ... <item128>
RPUSH <list-key> <item129> <item130> ... <item192>
重写程序使用类似的方法处理包含多个元素的有序集合键,以及包含多个键值对的哈希表键
aof_rewrite
函数可以很好地完成创建一个新AOF文件的任务,但是,因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞
因为Redis服务器使用单个线程 来处理 命令请求,所以如果由服务器直接调用aof_rewrite
函数的话,那么在重写AOF文件期间,服务期将无法处理客户端发来的命令请求
Redis 决定 将AOF重写程序(aof_rewrite
) 放到 子进程里执行,这样做可以同时达到两个目的:
不过,使用子进程也有一个问题需要解决,因为子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致
下面展示了一个AOF文件重写例子,当子进程开始进行文件重写时,数据库中只有k1一个键,但是当子进程完成AOF文件重写之后,服务器进程的数据库中已经新设置了k2、k3、k4三个键,因此,重写后的AOF文件和服务器当前的数据库状态并不一致,新的AOF文件只保存了k1一个键的数据,而服务器数据库现在却有k1、k2、k3、k4四个键
为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区 在服务器 创建子进程之后 开始使用,当Redis服务器 执行完一个 写命令之后,它会 同时 将这个写命令 发送给 AOF缓冲区 和 AOF重写缓冲区
也就是说,在 子进程 执行 AOF重写 期间,服务器进程需要执行以下三个工作:
这样一来可以保证:
appendfsync
选项),对现有AOF文件的处理工作会如常进行对于上面的例子,重写过程如下:
在整个AOF后台重写过程中,只有 信号处理函数 执行时 会对 服务器进程(父进程)造成阻塞
在其他时候,AOF后台重写都不会阻塞父进程,这将AOF重写对服务器性能造成的影响降到了最低
AOF重写降低了文件占用空间,除此之外,另一个目的是:更小的AOF文件可以更快地被Redis加载
BGREWRITEAOF的复杂度:O(N),其中N为Redis服务器所有数据库包含的键值对总数量
阅读参考:
执行AOF重写请求
如果当前进程正在执行AOF重写,请求不执行并返回如下响应:
ERR Background append only file rewriting already in progress
如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行,返回如下响应:
Background append only file rewriting scheduled
父进程执行fork创建子进程,开销等同于bgsave过程
主进程fork操作完成后,继续响应其他命令。所有修改命令依然写入AOF缓冲区 并根据 appendfsync策略同步到硬盘,保证原有AOF机制正确性
由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据
子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync
控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞
新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息
父进程把AOF重写缓冲区的数据写入到新的AOF文件
使用新AOF文件替换老文件,完成AOF重写
BGREWRITEAOF
手动触发AOF重写aof_current_size
aof_rewrite_base_size
aof_rewrite_perc
BGSAVE
命令(RDB持久化)/AOF持久化在执行;BGREWRITEAOF
在进行;aof_rewrite_min_size
(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size
大小;auto-aof-rewrite-percentage
参数,不设置默认为100%)如果前面三个条件都满足,并且 当前AOF文件大小 比 最后一次AOF重写时的大小 要大于 指定的百分比,那么触发自动AOF重写
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。