zoukankan      html  css  js  c++  java
  • redis--AOF

    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 可以由用户手动触发,也可以由服务器自动触发。
     
  • 相关阅读:
    深度学习一:安装MXnet包,实现MNIST手写数体识别
    深度学习二:Neural art:用机器模仿梵高
    【Stanford CNN课程笔记】1. Image Classification and Nearest Neighbor Classifier
    PHP之图形处理
    Ubuntu14.10+cuda7.0+caffe配置
    CentOS系统上编译、安装、配置OpenCV
    安装MXnet包,实现MNIST手写数体识别
    Neural art:用机器模仿梵高
    String to Integer (atoi)
    Wildcard Matching
  • 原文地址:https://www.cnblogs.com/Aiapple/p/7267336.html
Copyright © 2011-2022 走看看