redis AOF

REDIS AOF(Append Only File):

redis AOF(Append Only File) MODE 流程:

 1 相关配置参数:

  1) appendonly no: 是否启用AOF模式,默认不启用

  2) appendfilename "appendonly.aof": 只追加文件名字

  3) appendfsync: 当我们向只追加文件里追加内容时,调用相关写函数,实际上只是写到操作系统的 output buffer 中,操作系统还需要将output buffer 中的数据 flush 到磁盘上,才真正完成将本次数据写入磁盘,但是操作系统什么时候flush数据到磁盘是不确定的,和操作系统有关。有的系统不会保证及时刷到磁盘,这样一来就可能会丢失更多数据。但是可以显示的调用系统或库函数来进行这个操作,redis提供这个选项来供使用者配置合适刷output buffer 中的数据到磁盘.有三个选项:

    1) appendfsync always:每次当向只追加文件中写数据后,都会调用fsync把数据刷到磁盘,这个操作会比较慢,但是安全度最高
    2) appendfsync everysec: 每分钟定时调用fsync把数据刷到磁盘,折中方案,默认配置
    3) appendfsync no: 不会显示调用fsync将数据刷到磁盘,而是每次等待操作系统自己完成刷数据操作,最快,但是安全度没有另外两种好

   4) no-appendfsync-on-rewrite no: 当AOF 的fsync策略设置为 always or everysec 并且当一个后台保存进程正在对硬盘进行大量I/O操作时,在一些linux操作系统上,如果此时redis执行fsync操作,可能会导致长时间的block,及时在另外一个线程上执行fsync 仍会阻塞write操作。redis 专门提供了这么一个配置来决定是否再有以上情况下,redis主线程是否执行fsync操作。如果该值设置为 no ,则此时即使有后台保存进行存在,主线程也会执行fsync。虽然这回导致一些延时,但是会保证数据安全性,否则如果设置为yes 则在后台保存进程存在期间,redis主线程不会执行fsync 这样即使配置了appendfsync 为 always or everysec 也会导致可能最坏情况下丢失达到30秒(一般操作系统配置)左右的数据。所以一般设置为no.

  5) auto-aof-rewrite-percentage 100 

  6) auto-aof-rewrite-min-size 64mb

    上边两个选项提供了何时自动调用BGREWRITEAOF ,1是redis每次执行完rewrite操作后,都会记录当前的logsize,如果没有rewrite操作,则记录开始时AOF的文件大小。当之后的AOF文件大小,超过上次文件大小 auto-aof-rewrite-percentage 百分比时,则redis会自动调用BGREWRITEAOF ; 2 当1中的文件大小增长到达上一次大小的percentage时,如果文件的整个大小没有达到auto-aof-rewrite-min-size 的大小,则不会进行rewrite 操作。从而避免当数据量很小时频繁执行rewrite操作

  7) aof-rewrite-incremental-fsync yes: 当一个子进程rewrite AOF file时,如果该值设置为yes, 则该文件每产生32mb文件时,就会被fsync-ed,从而避免一次性fsync的延迟。   

  8) aof-load-truncated yes:

  9) aof-use-rdb-preamble yes: rewrite 进行时候,rewrite 文件分两种格式,1. 先 用 rdb 序列化,序列化结果写到rewriet 文件,然后期间积累的差异用追加aof命令格式 ,2 整个文件都是aof 本身的命令追加格式 ,第一种方式相比第二种更高效, 更节省空间.

2 相关命令: bgrewriteaof

 

3 AOF实现相关参数:

  1) server.aof_rewrite_scheduled: 当BGSAVE结束时候是否进行rewrite操作,1是进行,0不进行, 当执行bgrewriteaof 命令时,检查如果当前有子进程正在运行则设置此字段为1,当子进程结束时再执行该命令。

  2) server. aof_child_diff; rewrite 期间,子进程中收到的父进程传来的在此期间的diff accumulator,因为在子进程rewrite时,父进程还在处理客户端命令,因此会记录下在此期间的diff accumulator,并且时不时的穿向子进程,该值就是记录这些diff accumulator的,当然父进程中也会有类似的值.

  3) server.aof_rewrite_incremental_fsync:该值的含义见上边对应的配置选项含义.

  4)  关于rewrite期间,父子进程之间相互通信用时用到的相关参数

    server.aof_pipe_write_data_to_child: 子进程在rewrite开始时,复制了父进程内存数据,但是仅仅是当时的数据, 由于父进程继续处理客户端命令,在子进程把自己复制的数据写到aof文件时, 

      父进程还在继续运行,处理客户端命令,当处理了写相关的命令后,造成了和内存数据改变,导致和子进程当前的数据不一致。因此这部分写操作会由父进程缓存起来,等子进程完成工作后,

      再把这部分引起变化的命令数据追加到aof文件。但是为了优化,父进程会将这些积累的差异数据时不时地通过管道发送给子进程,由子进程存储起来,等待子进程先写完从父进程拷贝的内存数据到aof文件后,

      再把父进程通过管道发送给子进程的数据追加到aof文件,这样就替父进程分担了一部分压力,因为如果子进程不把差异数据追加到aof文件的话,这部分操作就会由父进程来做,如果在此期间父进程积累了

      大量差异数据,那么写aof文件操作就会非常耗时,可能导致主进程不能及时处理其他命令。
    server.aof_pipe_read_data_from_parent: 子进程通过该描述符读取父进程发送过来的差异数据;
    server.aof_pipe_write_ack_to_parent: 当子进程rewrite结束以后,用此描述符告知父进程不必再发送差异数据过来了(发送一个 '!' 给父进程);
    server.aof_pipe_read_ack_from_child: 父进程y通过次描述符读取上边子进程发过来的 '!';
    server.aof_pipe_write_ack_to_child: 父进程读取完子进程发过来的 '!' 后,通过该描述符给子进程一个确认, 同样是发送一个 '!' 给子进程;
    server.aof_pipe_read_ack_from_parent: 子进程通过词描述符读取父进程发来的确认 '!';
    server.aof_stop_sending_diff: 用来判断是否还需要发送数据给子进程,该值在父进程收到子进程发来的不用在发送数据的标识 '!' 后,置为1;

  5) server.aof_rewrite_time_start: 当前AOF rewrite 开始时间

  6) server.aof_fd: 当前的 AOF 文件的文件描述符

  7) server.aof_current_size: 当前写入到 AOF 文件的字节数

  8) server.aof_fsync_offset: 当前将AOF文件内容同步到disk的字节数,当调用fsync的时候,该函数会把对应文件描述符的output buffer 中的内容一次性全部刷到disk,所以如果调用成功该值的大小等于当前往AOF写完的数据量

  9) server.aof_last_fsync: 上一次成功将AOF文件内容同步到disk的时间戳

  10) server.aof_selected_db: 往AOF文件中追加的命令是对哪个db进行的操作

  11) server.aof_buf: AOF 模式开始后,redis处理的写命令会先存储到该缓存中,然后再同步到disk

  12) server.aof_flush_postponed_start: 当调用 flushAppendOnlyFile 时,先检查当前有没有fsync类型的bio 任务在执行,如果有的话,则需要延期进行该flush。用该值记录当前postpone_start开始时间, 之后根据该值判断是否可以flush 并且 从该时刻起,两秒后,不在延期,强制执行flush。该flush指,将server.aof_buffer 中的数据写到output buffer, 并且调用fsync 强制将output buffer 中的值flush 到disk.

  13) server.aof_delayed_fsync: 当 12) 中的情况发生两秒后,会强制执行flush,此时便会记录server.aof_delayed_fsync++ 用来记录延迟flush 的次数

  14) server.aof_no_fsync_on_rewrite: 当有子进程进行 rewrite 时候,主线程要不要进行 fsync 操作,

  15) server.aof_rewrite_perc: 与配置文件中的配置相对应,见上边配置文件的参数

  16) server.aof_rewrite_min_size: 与配置文件中的配置相对应,见上边配置文件的参数

  17) server.aof_rewrite_base_size: 上一次完成rewrite时候,rewrite文件大小.

  18) server.aof_current_size: aof 文件当前大小

  19) server.aof_rewrite_incremental_fsync: 与配置文件中的配置相对应,见上边配置文件的参数

  20) server.aof_use_rdb_preamble: 与配置文件中的配置相对应,见上边配置文件的参数

  21) server.aof_rewrite_buf_blocks: 用来记录在 子进程 rewrite 期间,父进程积累的新的写操作,这部分要在子进程rewrite 后,追加到新的rewrite 文件中

 

3 AOF 过程(有rewrite 版本的):

  1) 流程: 假设服务器启动前 redis.config 中已经配置了 appendonlyfile yes 开启了 aof 持久化模式:

     (1) 服务器开始启动 : loadAppendOnlyFile(char *filename); 该阶段如果没有则创建一个空文件;  如果有的话,则在此阶段通过按顺序执行aof 文件中的各个命令,重新恢复了数据库,。

     (2) 服务器开始运行,执行客户端写相关命令时, 调用 feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); 此命令将对哪个db,进行了什么写操作,记录到 server.aof_buffer 中, (当然如果此时有子进程进行 rewrite 操作,则将同样的内容也写到 差异缓存中, 后面再详细讲).

1 if (dictid != server.aof_selected_db) {
2         char seldb[64];
3 
4         snprintf(seldb,sizeof(seldb),"%d",dictid);
5         buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
6             (unsigned long)strlen(seldb),seldb);
7         server.aof_selected_db = dictid;
8     }

  先写入一个选库操作,再写对该库进行了什么写操作。格式是和客户端传给服务器的命令格式是一样的,因为前面LoadAppendOnlyFile 时候,就是一条一条的读命令,然后用处理客户端命令方式,再次执行一遍该命令,最终恢复整个数据库. 注意该函数进行的操作仅仅是将命令写到 aof_buffer, 此时还没有真正同步到文件,进而同步到磁盘,因此还需要下面的操作.

     (3) flushAppendOnlyFile(int force) 上面一步只是把命令写进缓存中,该函数将缓存中的内容写到文件,再写到磁盘,注意这是两个步骤,调用write 把内容写到文件本身的output buffer ,并不意味着数据就写到磁盘了,因此为了保证可靠性,redis显示调用fsync 将文件output buffer 中的内容刷到磁盘,而不是等待操作系统自己进行写磁盘操作。回头看最上边配置文件里的选项有一项 appendfsync 该选项可以配置三个值,具体见上边解释。

      flush 基本思路: 1 aof_buffer 里有数据则先将aof_buffer 中的数据写到aof 文件output buffer(即调用系统函数write),之后再将aof 文件 output buffer 的内容 fsync 到磁盘. 注意将aof_buffer数据写到aof文件一直是主线程进行的,而fsync 操作,视appendfsync 的配置决定是主线程调用还是bio子线程调用.

             2 aof_buffer 里没有数据了,但是aof 文件的 output buffer 里还有内容没刷到磁盘, 则会择机 fsync. 

             3 由于 appendfsync 的值不同导致fsync 的策略不同。

                当为 everysecond 时,表示每秒fsync一次。第一次写aof时,当aof_buffer 中有数据,并且主线程成功将数据写到aof文件之后,主线程创建fsync bio 任务,让子线程执行 fsync 操作。当再次调用flushAppendOnlyFile操作时,因为是 everysecond, 会先检查当前是否有bio fsync任务没有执行完,如果有的的话,并且调用flushAppendOnlyFile的force 为0 表示不强制fsync, 则此时标记 aof_flush_postponed_start 为当前时间,表示延迟flush 并且函数返回不进行写 aof_buffer 到aof文件操作,因为调用fsync时,无论是主线程还是子线程都会阻塞在write操作 从而降低redis效率。但是延时多长时间呢,如果延时时间太长,在此期间万一出现什么意外导致aof_buffer 中的数据没来得及急写到aof 文件,那么这样一来aof的效果就会和rdb差不多了,aof 和rdb比起来 数据持久化更加有保障是其一大有点。鉴于这个问题,redis 规定,当下次flushAppendOnlyFile 并且force 为0 时,如果当前时间 server.unixtime - 延时开始时间 aof_flush_postponed_start >= 2 时, 则强制将aof_buffer 中的数据写到aof文件,从而也可以得出一个结论,那就是即使配置了每秒同步数据到磁盘,但在此情况下会有两秒的数据丢失。强制写到aof 之后,则再次尝试通过 bio 创建fsync 任务,让子线程进行aof 文件的fsync,因为该配置是 everysecond 在创建bio fsync任务时,是server.unixtime > server.aof_last_fsync,这两个值得单位是秒,所以当前者大于后者时,正好用来可以判断过了一秒,保证每秒进行fsync。

                当为 always 时,表示每次写完aof_buffer 则在主线程进行fsync。此时在调用完feedAppendOnlyFile后就会同步数据到磁盘,因此这种方式是最安全的,但是也是最耗时的,影响redis效率

             4 又回到 2,因为有不同的 fsync 策略,所以当aof_buffer 中没有数据了,但是同步到磁盘的数据小于当前aof文件的大小时,表示还有没同步到磁盘的数据在aof文件output buffer。这种情况出现在 appendfsync 为 everysecond 并且 有数据没同步到磁盘,并且距离上一次同步到磁盘过了一秒钟了,并且当前bio 没有fsync 任务,此时则会尝试进行 fsync 操作,将写到aof 文件output buffer 数据同步到磁盘上

             5 总结起来就是 服务器处理的客户端写相关命令 append到 aof_buffer, 然后根据 appendfsync 配置的策略择机将aof_buffer 写到 aof 文件的output buffer, 然后再根据appendfsync 的策略 择机fsync 到磁盘.

  2)rewrite: 

    (1) what is rewrite: 简单点儿讲就是通过某种方式重新生成一份 aof 文件.

    (2) why rewrite:前面讲过 aof 文件是一份追加式文件,把对数据库的写相关操作记录下来,之后重新加载,执行命令,恢复数据库。那么就会出现这种情况: 某些key的重复操作很多,例如执行多次set key value. 这里key相同value不同,key 的值是最后一次set的结果,但是在aof中就会有多个重复操作,导致aof文件越来越臃肿。这时候就需要在适当的时机,基于当前服务器内存数据,重新写一份aof文件,去除这些"冗余"数据,精简aof文件。另一方面,正如redis官方文档提到的,aof文件追加写命令容易,但是重新加载aof文件时候,有时候会出现bug 导致不能正确恢复数据,因此时不时的rewrite 也有利于保证数据持久化的正确性。

    (3) how to rewrite: 概括: 1 先创建子进程把当前数据库内容用某种格式保存起来;2 子进程保存期间,父进程通过管道同步部分或所有的在这过程积累的写命令到子进程,子进程结束前将这部分数据一起写进aof文件;3 子进程保存结束,父进程如果还有积累的写命令,则父进程把这部分数据保存到aof文件。

              此时aof格式为:

              part1: |------------------rdb or aof 方式保存当前数据库内容的数据--------------|

                              + 

              part2: |-------------------如果此时父进程有累积的写命令的话(没有的话也没有这部分数据),如果顺利传给了子进程,则子进程在完成 part1 操作后,还要把这部分命令追加到 part1 后(正常aof追加写命令格式)---------|

                              +

              part3: |------------------子进程完成part2 后,父进程如果这时还有累积的写命令,则把这部分写命令追加到 part2 步骤的数据后---------------------------|

             保存当前数据库数据时有两种方式 利用rdb格式保存 和 aof命令追加格式。即我aof文件的开头可以是rdb的格式,然后 + 累积的写命令格式 或者是 全部都是命令追加格式,此时aof 开头的命令会是根据当前数据库内容及数据类型,按照对应的数据类型转换成对应的写命令保存在aof文件。写 part1 时 用 rdb 格式,则序列化和加载时反序列化的速度会更快。这样rewrite之后,用配置的aof文件名替换新rewrite之后的文件

名.

    4) when to rewrite: 1 当满足配置文件中配置的rewrite条件时,系统会自动rewrite,2 当客户端中途配置开启aof模式时候,系统会先rewrite 一次。

    5) aof 文件的缺点.

              

          

       

 

posted @ 2021-05-03 10:59  mr_yu  阅读(253)  评论(0编辑  收藏  举报