Redis 分别提供了 RDB 和 AOF 两种持久化机制:
  • RDB 将数据库的快照( snapshot)以二进制的方式保存到磁盘中。

    • 相当于MySQL binlog 的 raw模式
  • AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF
    • 相当于MySQL binlog 的 satement模式
文件,以此达到记录数据库状态的目的。
 
 

AOF命令同步

Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。
为了处理的方便,AOF 文件使用网络通讯协议的格式来保存这些命令。
 
同步命令到 AOF 文件的整个过程可以分为三个阶段:
 
  • 命令传播:
    • Redis将执行完的命令、命令的参数、命令的参数个数等信息发送到AOF程序中。
  • 缓存追加:
    • AOF程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的AOF缓存中。
  • 文件写入和保存:
    • AOF缓存中的内容被写入到AOF文件末尾,如果设定的AOF保存条件被满足的话,
    • fsync函数或者fdatasync函数会被调用,将写入的内容真正地保存到磁盘中
 
命令传播过程
if (execRedisCommand(cmd, argv, argc) == EXEC_SUCCESS):
if aof_is_turn_on():
# 传播命令到 AOF 程序
propagate_aof(cmd, argv, argc) if replication_is_turn_on():
# 传播命令到 REPLICATION 程序
propagate_replication(cmd, argv, argc)
缓存追加
当命令被传播到 AOF 程序之后,程序会根据命令以及命令的参数,将命令从字符串对象转换回原来的协议文本。
redisServer 结构维持着 Redis 服务器的状态,
aof_buf域则保存着所有等待写入到AOF文件的协议文本:
 
缓存追加过程分为三步:
  • 接受命令、命令的参数、以及参数的个数、所使用的数据库等信息
  • 将命令还原成 Redis 网络通讯协议
  • 将协议文本追加到 aof_buf ( Redis 中 AOF 缓存)末尾
 
文件写入和保存
每当服务器常规任务函数被执行、或者事件处理器被执行时,aof.c/flushAppendOnlyFile 函数都会被调用,
这个函数执行以下两个工作:
  • WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF文件
    • AOF 文件相当于MySQL redo log 缓存,然后使用一些策略,提升持久化到磁盘的效率
    • 因为AOF 是后台进程,此时AOF 文件 是后台进程的 缓存
  • SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
 
AOF 保存模式

Redis 目前支持三种 AOF 保存模式,它们分别是:
AOF_FSYNC_NO :不保存。
AOF_FSYNC_EVERYSEC :每一秒钟保存一次。
AOF_FSYNC_ALWAYS :每执行一个命令保存一次
 
不保存
每次调用 flushAppendOnlyFile 函数,WRITE 都会被执行,但 SAVE会被略过
在这种模式下,SAVE 只会在以下任意一种情况中被执行:
  • Redis 被关闭
  • AOF功能被关闭
  • 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。
 
每秒保存一次
 
在这种模式中,SAVE 原则上每隔一秒钟就会执行一次,
因为SAVE操作是由后台子线程调用的,所以它不会引起服务器主进程阻塞。
 
"原则上", 在实际运行中,程序在这种模式下对 fsync或 fdatasync 的调用并不是每秒一次,
它和调用flushAppendOnlyFile函数时Redis所处的状态有关。
 
每当 flushAppendOnlyFile 函数被调用时,可能会出现以下四种情况:
  • 子线程正在执行 SAVE ,并且:
    • 1. 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的SAVE 。
      • 在情况 1 中发生故障停机,那么用户最多损失小于 2 秒内所产生的所有数据。
    • 2. 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。
      • 注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里WRITE 会比平时阻塞更长时间
      • 在情况 2 中发生故障停机,那么用户损失的数据是可以超过 2 秒的。
  • 子线程没有在执行 SAVE ,并且:
    • 3. 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。
    • 4. 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。
 
Redis 官网上所说的,AOF 在“每一秒钟保存一次”时发生故障,只丢失 1 秒钟数据的说法,实际上并不准确。
 
流程图来说明
 

每执行一个命令保存一次
  • 每次执行完一个命令之后,WRITE 和 SAVE 都会被执行。
  • 另外,因为 SAVE 是由 Redis 主进程执行的
    • 所以在SAVE执行期间,主进程会被阻塞,不能接受命令请求
 
 
AOF 保存模式对性能和安全性的影响
对于三种 AOF 保存模式,它们对服务器主进程的阻塞情况如下:
  • 不保存( AOF_FSYNC_NO):
    • 写入和保存都由主进程执行,两个操作都会阻塞主进程。
  • 每一秒钟保存一次( AOF_FSYNC_EVERYSEC):
    • 写入操作由主进程执行,阻塞主进程。
    • 保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。
  • 每执行一个命令保存一次( AOF_FSYNC_ALWAYS):和模式 AOF_FSYNC_NO 一样。
 
因为阻塞操作会让 Redis 主进程无法持续处理请求,所以一般说来,阻塞操作执行得越少、完成得越快,Redis 的性能就越好。

所以说Redis 还是不能保证数据完全不丢失
 
AOF 文件的读取和数据还原
AOF 文件保存了 Redis 的数据库状态,而文件里面包含的都是符合 Redis 通讯协议格式的命令文本。
这也就是说,只要根据 AOF 文件里的协议,重新执行一遍里面指示的所有命令,就可以还原Redis 的数据库状态了。
 
Redis 读取 AOF 文件并还原数据库的详细步骤如下:
  • 创建一个不带网络连接的伪客户端( fake client)。
  • 读取 AOF 所保存的文本,并根据内容还原出命令、命令的参数以及命令的个数。
  • 根据命令、命令的参数和命令的个数,使用伪客户端执行该命令。
  • 执行 2 和 3 ,直到 AOF 文件中的所有命令执行完毕。
完成第 4 步之后,AOF 文件所保存的数据库就会被完整地还原出来
 
注意,因为 Redis 的命令只能在客户端的上下文中被执行,因此创建一个伪客户端
 
读取和还原过程伪代码:

def READ_AND_LOAD_AOF():

     # 打开并读取 AOF 文件
file = open(aof_file_name)
while file.is_not_reach_eof(): # 读入一条协议文本格式的 Redis 命令
cmd_in_text = file.read_next_command_in_protocol_format() # 根据文本命令,查找命令函数,并创建参数和参数个数等对象
cmd, argv, argc = text_to_command(cmd_in_text) # 执行命令
execRedisCommand(cmd, argv, argc) # 关闭文件
file.close()
当程序读入这个 AOF 文件时,它首先执行 SELECT 命令,确保数据还原到正确的数据库上。
 
AOF 重写

所谓的“重写”其实是一个有歧义的词语,实际上,AOF 重写并不需要对原有的 AOF 文件进行
任何写入和读取,它针对的是数据库中键的当前值。
 
例子:
SADD animal cat // {cat}
SADD animal dog panda tiger // {cat, dog, panda, tiger}
SREM animal cat // {dog, panda, tiger}
SADD animal cat lion // {cat, lion, dog, panda, tiger}
实际上可以用
SADD animal cat lion dog panda tiger 一条命令替代; 这比之前的四条命令调用要大大减少。
 
根据键的类型,使用适当的写入命令来重现键的当前值,这就是 AOF 重写的实现原理。
重写过程:定期获取某个键的实际值,然后写入通过命令 + 参数 + 值 写入到AOF文件中;
 
伪代码:
def AOF_REWRITE(tmp_tile_name):

     f = create(tmp_tile_name)

     # 遍历所有数据库
for db in redisServer.db: # 如果数据库为空,那么跳过这个数据库
if db.is_empty(): continue # 写入 SELECT 命令,用于切换数据库
f.write_command("SELECT " + db.number) # 遍历所有键
for key in db:
# 如果键带有过期时间,并且已经过期,那么跳过这个键
if key.have_expire_time() and key.is_expired(): continue if key.type == String: # 用 SET key value 命令来保存字符串键
value = get_value_from_string(key) f.write_command("SET " + key + value) elif key.type == List: # 用 RPUSH key item1 item2 ... itemN 命令来保存列表键
item1, item2, ..., itemN = get_item_from_list(key) f.write_command("RPUSH " + key + item1 + item2 + ... + itemN) elif key.type == Set: # 用 SADD key member1 member2 ... memberN 命令来保存集合键
member1, member2, ..., memberN = get_member_from_set(key) f.write_command("SADD " + key + member1 + member2 + ... + memberN) elif key.type == Hash: # 用 HMSET key field1 value1 field2 value2 ... fieldN valueN 命令来保存哈希键
field1, value1, field2, value2, ..., fieldN, valueN =get_field_and_value_from_hash(key) f.write_command("HMSET " + key + field1 + value1 + field2 + value2 + fieldN + valueN)
elif key.type == SortedSet: # 用 ZADD key score1 member1 score2 member2 ... scoreN memberN # 命令来保存有序集键
score1, member1, score2, member2, ..., scoreN, memberN = get_score_and_member_from_sorted_set(key) f.write_command("ZADD " + key + score1 + member1 + score2 + member + scoreN + memberN) else:
raise_type_error() # 如果键带有过期时间,那么用 EXPIREAT key time 命令来保存键的过期时间 if key.have_expire_time():
f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())
# 关闭文件
f.close()
AOF 后台重写
 
上面AOF重写程序,是在主程序被调用的,会阻塞服务器请求处理,
这是不希望看到的,于是将AOF重写放到(后台)子程序执行是必要的
好处是:
  • 子进程进行 AOF 重写期间,主进程可以继续处理命令请求
  • 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。
 
问题
  • 数据不一致性

    • 因为子进程在进行AOF重写期间,主进程还需要处理命令
    • 这就可能造成数据库当前数据和重写后的AOF文件数据不一致
 
解决
  • Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用。
  • Redis主进程在接到新的写命令之后,处理会将这个命令的协议内容追加AOF重写缓存
    • 追加到现有AOF文件,就是遵循原本逻辑 命令传递->追加缓存->文件保存和写入
  • 还会追加到这个缓存中:
 
图解
 

 
这样一来
  • 现有的AOF功能会继续执行
  • 所有对数据库进行修改的命令都会被记录到AOF重写缓存中。
 
当子进程完成AOF重写之后并生成新的AOF文件,它会向父进程发送一个完成信号,
父进程会调用一个函数:
  • 将AOF重写缓存中的内容全部写入到新AOF文件中

    • 完成之后,现有AOF文件,新AOF文件和数据库三者的状态完全一致
  • 对新的AOF文件进行改名,覆盖原有的AOF文件。
    • 程序完成新旧两个AOF文件交替
 
整个AOF后台重写过程中,只有最后的写入缓存和改名操作会造成主进程阻塞
(和pt-online-schema-change 相似)
 
上面就是AOF后台重写,也是BGREWRITEAOF命令的工作原理。
 
AOF后台重写的意义,就在与除去了很多中间操作,在数据库恢复的时候,效率会很高。
 
AOF 后台重写的触发条件
 
AOF重写可以由用户通过调用BGREWRITEAOF 手动触发
 
另外,服务器在 AOF 功能开启的情况下,会维持以下三个变量:
  • 记录当前 AOF 文件大小的变量 aof_current_size
  • 记录最后一次 AOF 重写之后,AOF 文件大小的变量 aof_rewirte_base_size
  • 增长百分比变量 aof_rewirte_perc
 
每次当 serverCron 函数执行时,检查是否全部满足以下条件
  • 没有 BGSAVE 命令在进行。
  • 没有 BGREWRITEAOF 在进行
  • 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)
  • 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。
 
默认情况下,增长百分比为 100% ,也即是说,如果前面三个条件都已经满足,并且当前 AOF
文件大小比最后一次 AOF 重写时的大小要大一倍的话,那么触发自动 AOF 重写
 
小结
  • AOF 文件通过保存所有修改数据库的命令来记录数据库的状态。
  • AOF 文件中的所有命令都以 Redis 通讯协议的格式保存。
  • 不同的 AOF 保存模式对数据的安全性、以及 Redis 的性能有很大的影响。
  • AOF 重写的目的是用更小的体积来保存数据库状态,整个重写过程基本上不影响 Redis主进程处理命令请求。
  • AOF 重写是一个有歧义的名字,实际的重写工作是针对数据库的当前值来进行的,程序既不读写、也不使用原有的 AOF 文件。
  • AOF 可以由用户手动触发,也可以由服务器自动触发。
 

redis--AOF的更多相关文章

  1. 深入剖析 redis AOF 持久化策略

    本篇主要讲的是 AOF 持久化,了解 AOF 的数据组织方式和运作机制.redis 主要在 aof.c 中实现 AOF 的操作. 数据结构 rio redis AOF 持久化同样借助了 struct ...

  2. Redis AOF文件

    [Redis AOF文件] 1.关于AOF AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集. AOF 文件中的命令全部以 Redis 协议的格式来保存 ...

  3. Redis AOF 全持久化

    简介: Redis AOF 持久化,将每次接收到更改 redis 数据的操作都记录到一个 aof 文件,当服务器意外宕机或 redis 服务器非法关闭时,不会丢失数据. 可以做到数据安全化,但是性能会 ...

  4. redis AOF保存机制

    网上说AOF有三种保存方式,不自动保存.每秒自动保存.每命令自动保存. 其中每秒自动保存这个看起来很美好,但是可能会被各种IO的时间所延迟,所以究竟是怎么判断每秒保存的,并不是太明白,故有此文. AO ...

  5. 解决redis aof文件过大的问题

    执行BGREWRITEAOF命令对redis的AOF进行重写 redis-cli BGREWRITEAOF 相关解释: Redis的AOF机制有点类似于Mysql binlog,是Redis的提供的一 ...

  6. redis:aof恢复与rdb服务器间迁移

    1. aof恢复与rdb服务器间迁移: 1.1. Aof恢复: 如果不小心执行了flushdb或flushall了怎么办? (1)立马执行命令:shutdown nosave 关闭服务器,为了防止其他 ...

  7. REdis AOF文件结构分析

    REdis-4.0之前的AOF文件没有文件头,而从REdis-4.0开始AOF文件带有一个文件头,文件头格式和RDB文件头相同. REdis-4.0版本,如果开启aof-use-rdb-preambl ...

  8. redis aof和rdb区别

    转自https://blog.csdn.net/m0_38110132/article/details/76906422 1.前言 最近在项目中使用到Redis做缓存,方便多个业务进程之间共享数据.由 ...

  9. redis AOF 和RDB

    AOF定义:以日志的形式记录每个操作,将Redis执行过的所有指令全部记录下来(读操作不记录),只许追加文件但不可以修改文件,Redis启动时会读取AOF配置文件重构数据 换句话说,就是Redis重启 ...

  10. redis aof文件过大问题

    http://www.itnose.net/detail/6682725.html 最近新安装了一台redis,版本为redis-3.2.5 数据盘用的是固态硬盘. 之前用的是普通硬盘,redis日志 ...

随机推荐

  1. GCC 中 -L、-rpath和-rpath-link的区别

    GCC 中 -L.-rpath和-rpath-link的区别 来源 http://blog.csdn.net/q1302182594/article/details/42102961 关于这3个参数的 ...

  2. cf 834 E. Ever-Hungry Krakozyabra

    cf 834 E. Ever-Hungry Krakozyabra(爆搜+数位dp) 题意: 定义一种inedible tail为一个数把每一位数字按不降的顺序排列后,去掉前导0组成的序列 比如570 ...

  3. WebSocket贪吃蛇例子学习

    在Tomcat7.0.64下的examples文件夹内,有多人贪吃蛇的例子. Multiplayer snake 这是一个多人在线小游戏,客户端通过操作上下左右键指挥自己的蛇,如果碰到别的蛇就死掉.还 ...

  4. Eclipse中使用Maven创建Web时错误

    一.问题描述 使用Eclipse创建Maven项目时,报一下错误,不能创建成功. 二.问题原因 错误详细描述是说 Could not resolve archetype org.apache.mave ...

  5. POJ3522 Slim Span

    Slim Span Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 7462   Accepted: 3959 Descrip ...

  6. 第2章 掌握C++

    参考: https://blog.csdn.net/u014162133/article/details/46573873 1.C++主要特点: 封装性(Encapsulation):把数据与操作数据 ...

  7. Linux内核实践之序列文件【转】

    转自:http://blog.csdn.net/bullbat/article/details/7407194 版权声明:本文为博主原创文章,未经博主允许不得转载. 作者:bullbat seq_fi ...

  8. usb驱动的基本结构和函数简介【转】

    转自:http://blog.csdn.net/jeffade/article/details/7698404 几个重要的结构 struct--接口 struct usb_interface { /* ...

  9. MySQL学习——基础

    本文是MySQL的基础知识. Linux启动MySQL服务命令 : service mysql start Linux关闭MySQL服务命令 : service mysql stop 登录MySQL命 ...

  10. Windows8 上用Ubuntu-Ubuntu启动SSH

    公司刚给配了一个电脑,华硕的超级本8个G的内存,很强大的了,但是系统是64位的windows 8,我用wubi.exe直接安装到系统上,但是开机启动的时候总是下面这个错误,去Ubuntu社区请教,结论 ...