zoukankan      html  css  js  c++  java
  • redis AOF保存机制

    网上说AOF有三种保存方式,不自动保存、每秒自动保存、每命令自动保存。

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

    AOF 命令同步

    Redis 将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件, 以此达到记录数据库状态的目的, 为了方便起见, 我们称呼这种记录过程为同步。

    举个例子, 如果执行以下命令:

    redis> RPUSH list 1 2 3 4
    (integer) 4
    
    redis> LRANGE list 0 -1
    1) "1"
    2) "2"
    3) "3"
    4) "4"
    
    redis> KEYS *
    1) "list"
    
    redis> RPOP list
    "4"
    
    redis> LPOP list
    "1"
    
    redis> LPUSH list 1
    (integer) 3
    
    redis> LRANGE list 0 -1
    1) "1"
    2) "2"
    3) "3"

    那么其中四条对数据库有修改的写入命令就会被同步到 AOF 文件中:

    RPUSH list 1 2 3 4
    
    RPOP list
    
    LPOP list
    
    LPUSH list 1

    为了处理的方便, AOF 文件使用网络通讯协议的格式来保存这些命令。

    比如说, 上面列举的四个命令在 AOF 文件中就实际保存如下:

    *2
    $6
    SELECT
    $1
    0
    *6
    $5
    RPUSH
    $4
    list
    $1
    1
    $1
    2
    $1
    3
    $1
    4
    *2
    $4
    RPOP
    $4
    list
    *2
    $4
    LPOP
    $4
    list
    *3
    $5
    LPUSH
    $4
    list
    $1
    1

    除了select命令是 AOF 程序自己加上去的之外, 其他命令都是之前我们在终端里执行的命令。

    同步命令到 AOF 文件的整个过程可以分为三个阶段:

    1. 命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。
    2. 缓存追加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的 AOF 缓存中。
    3. 文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。

    以下几个小节将详细地介绍这三个步骤。

    命令传播

    当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。

    比如说, 要执行命令 SET KEY VALUE , 客户端将向服务器发送文本 "*3 $3 SET $3 KEY $5 VALUE " 。

    服务器在接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文本转换为 Redis 字符串对象(StringObject)。

    比如说, 针对上面的 SET 命令例子, Redis 将客户端的命令指针指向实现 SET 命令的 setCommand 函数, 并创建三个 Redis 字符串对象, 分别保存 SET 、 KEY 和 VALUE 三个参数(命令也算作参数)。

    每当命令函数成功执行之后, 命令参数都会被传播到 AOF 程序, 以及 REPLICATION 程序(本节不讨论这个,列在这里只是为了完整性的考虑)。

    这个执行并传播命令的过程可以用以下伪代码表示:

    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)
    

    以下是该过程的流程图:

    digraph propagate {

    node [shape = plaintext, style = filled];

    edge [style = bold];

    // node 

    exec [label = "命令执行成功", fillcolor = "#FADCAD"];

    aof_choice [label = "AOF
 功能已打开?", shape = diamond, fillcolor = "#95BBE3"];

    propagate_aof [label = "传播命令到 AOF 程序", fillcolor = "#A8E270"];

    replication_choice [label = "REPLICATION
 功能已打开?", shape = diamond, fillcolor = "#95BBE3"];

    propagate_replication [label = "传播命令到 REPLICATION 程序", fillcolor = "#A8E270"];

    remaind_jobs [label = "处理后续步骤:
清理资源、
等等", fillcolor = "#FADCAD"];

    // edge

    exec -> aof_choice;

    aof_choice -> propagate_aof [label = "是"];

    propagate_aof -> replication_choice;

    aof_choice -> replication_choice [label = "否"];

    replication_choice -> remaind_jobs [label = "否"];

    replication_choice -> propagate_replication [label = "是"];

    propagate_replication -> remaind_jobs;
}

    缓存追加

    当命令被传播到 AOF 程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的协议文本。

    比如说, 如果 AOF 程序接受到的三个参数分别保存着 SET 、 KEY 和 VALUE 三个字符串, 那么它将生成协议文本"*3 $3 SET $3 KEY $5 VALUE " 。

    协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。

    redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF 文件的协议文本:

    struct redisServer {
    
        // 其他域...
    
        sds aof_buf;
    
        // 其他域...
    };
    

    至此, 追加命令到缓存的步骤执行完毕。

    综合起来,整个缓存追加过程可以分为以下三步:

    1. 接受命令、命令的参数、以及参数的个数、所使用的数据库等信息。
    2. 将命令还原成 Redis 网络通讯协议。
    3. 将协议文本追加到 aof_buf 末尾。

    文件写入和保存

    每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:

    WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。

    SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

    两个步骤都需要根据一定的条件来执行, 而这些条件由 AOF 所使用的保存模式来决定, 以下小节就来介绍 AOF 所使用的三种保存模式, 以及在这些模式下, 步骤 WRITE 和 SAVE 的调用条件。

    AOF 保存模式

    Redis 目前支持三种 AOF 保存模式,它们分别是:

    1. AOF_FSYNC_NO :不保存。
    2. AOF_FSYNC_EVERYSEC :每一秒钟保存一次。
    3. AOF_FSYNC_ALWAYS :每执行一个命令保存一次。

    以下三个小节将分别讨论这三种保存模式。

    不保存

    在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。

    在这种模式下, SAVE 只会在以下任意一种情况中被执行:

    • Redis 被关闭
    • AOF 功能被关闭
    • 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

    这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

    每一秒钟保存一次

    在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。

    注意, 在上一句的说明里面使用了词语“原则上”, 在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。

    每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:

    • 子线程正在执行 SAVE ,并且:

      1. 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。
      2. 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。
    • 子线程没有在执行 SAVE ,并且:

      1. 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。
      2. 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。

    可以用流程图表示这四种情况:

    digraph flush {

    node [shape = plaintext, style = filled, fillcolor = "#FADCAD"];

    edge [style = bold];

    //

    SAVE_running_choice [label = "SAVE 正在执行?", shape = diamond, fillcolor = "#A8E270"];

    over_2_second_choice [label = "运行时间
超过 2 秒?", shape = diamond, fillcolor = "#95BBE3"];

    not_over_2_second [label = "情况 1 :
函数直接返回
 不执行 WRITE 或
新的 SAVE"];

    over_2_second [label = "情况 2 :
执行 WRITE 
 但不执行新的 SAVE 
"];

    SAVE_running_choice -> over_2_second_choice [label = "是"];

    over_2_second_choice -> not_over_2_second [label = "否"];

    over_2_second_choice -> over_2_second [label = "是"];

    finish_over_2_second [label = "距离上次 SAVE
 执行成功
超过 1 秒?", shape = diamond, fillcolor = "#95BBE3"];

    no [label = "情况 3 :
 执行 WRITE 
 但不执行新的 SAVE "];

    yes [label = "情况 4 :
 执行 WRITE 和
新的 SAVE
"];

    SAVE_running_choice -> finish_over_2_second [label = "否"];

    finish_over_2_second -> yes [label = "是"];

    finish_over_2_second -> no [label = "否"];

}

    根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。

    如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。

    Redis 官网上所说的, AOF 在“每一秒钟保存一次”时发生故障, 只丢失 1 秒钟数据的说法, 实际上并不准确。

    每执行一个命令保存一次

    在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。

    另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。

    AOF 保存模式对性能和安全性的影响

    在上一个小节, 我们简短地描述了三种 AOF 保存模式的工作方式, 现在, 是时候研究一下这三个模式在安全性和性能方面的区别了。

    对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:

    1. 不保存(AOF_FSYNC_NO):写入和保存都由主进程执行,两个操作都会阻塞主进程。
    2. 每一秒钟保存一次(AOF_FSYNC_EVERYSEC):写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。
    3. 每执行一个命令保存一次(AOF_FSYNC_ALWAYS):和模式 1 一样。

    因为阻塞操作会让 Redis 主进程无法持续处理请求, 所以一般说来, 阻塞操作执行得越少、完成得越快, Redis 的性能就越好。

    模式 1 的保存操作只会在AOF 关闭或 Redis 关闭时执行, 或者由操作系统触发, 在一般情况下, 这种模式只需要为写入阻塞, 因此它的写入性能要比后面两种模式要高, 当然, 这种性能的提高是以降低安全性为代价的: 在这种模式下, 如果运行的中途发生停机, 那么丢失数据的数量由操作系统的缓存冲洗策略决定。

    模式 2 在性能方面要优于模式 3 , 并且在通常情况下, 这种模式最多丢失不多于 2 秒的数据, 所以它的安全性要高于模式 1 , 这是一种兼顾性能和安全性的保存方案。

    模式 3 的安全性是最高的, 但性能也是最差的, 因为服务器必须阻塞直到命令信息被写入并保存到磁盘之后, 才能继续处理请求。

    综合起来,三种 AOF 模式的操作特性可以总结如下:

    模式WRITE 是否阻塞?SAVE 是否阻塞?停机时丢失的数据量
    AOF_FSYNC_NO 阻塞 阻塞 操作系统最后一次对 AOF 文件触发 SAVE 操作之后的数据。
    AOF_FSYNC_EVERYSEC 阻塞 不阻塞 一般情况下不超过 2 秒钟的数据。
    AOF_FSYNC_ALWAYS 阻塞 阻塞 最多只丢失一个命令的数据。
  • 相关阅读:
    资源-python 视频下载大全
    ubuntu 16.04(操作应用) -软件卸载
    资源-简历的相关知识
    centos (命令操作)-crontab命令
    ubuntu 16.04 (软件应用)-输入法
    ntp时间同步
    lvm空间扩容
    目录知识
    Linux下安装maven
    elasticsearch安装pinyin模块
  • 原文地址:https://www.cnblogs.com/bethunebtj/p/5744514.html
Copyright © 2011-2022 走看看