`
DiaoCow
  • 浏览: 241572 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

《Redis源码学习笔记》AOF

阅读更多
《Redis源码学习笔记》文章列表

由于图片较大,缩放较为模糊,请双击打开查看原图 ^_^

Redis提供两种持久化方式,RDB和AOF;与RDB不同,AOF可以完整的记录整个数据库,而不像RDB只是数据库某一时刻的快照;

那么AOF模式为什么可以完整的记录整个数据库呢?

原理:在AOF模式下,Redis会把执行过的每一条更新命令记录下来,保存到AOF文件中;当Redis需要恢复数据库数据时,只需要从之前保存的AOF文件中依次读取命令,执行即可 eg.

我们执行了以下命令:
redis 127.0.0.1:6379> set name diaocow
OK
redis 127.0.0.1:6379> lpush country china usa
(integer) 4

这时候在AOF文件中的类容类似下面:
*3\r\n$3\r\nset\r\n$4\r\nname\r\n$7\r\ndiaocow\r\n
*4\r\n$5\r\nlpush\r\n$7\r\ncountry\r\n$5\r\nchina\r\n$3\r\nusa\r\n

看了上面的内容,我想不用我过多解释,你也能大致猜出AOF协议格式,因为它实在太简单明了了

Redis把更新命令记录到AOF文件,分为两个阶段:

阶段1:把更新命令写入aof缓存



def processCommand(cmd, argc, argv):
    # 执行命令
    call(cmd, argc, argv)
    # 该命令变更了键空间并且AOF模式打开
    if redisServer.update_key_space and redisServer.aof_state & REDIS_AOF_ON:
        feedAppendOnlyFile(cmd, argc, argv) 

def feedAppendOnlyFile(cmd, argc, argv):
    # 把命令转换成AOF协议格式
    aofCmdStr = getAofProtocolStr(cmd, argc, argv)
    redisServer.aof_buf.append(aofCmdStr )

    # 存在一个子进程正在进行AOF_REWRITE(关于AOF_REWRITE,稍后详说)
    if redisServer.aof_child_pid != -1:
        redisServer.aof_rewrite_buf_blocks.append(aofCmdStr )

阶段2: 把aof缓存写入文件

当我们开始下一次事件循环之前,redis会把AOF缓存中的内容写入到文件:
def flushAppendOnlyFile(force):  
    if len(redisServer.aof_buf) == 0:
        return
    # 把缓存数据写入文件  
    if writeByPolicy(force, redisServer.aof_fsync):   
        write(redisServer.aof_fd, redisServer.aof_buf, len(redisServer.aof_buf))   
    # 同步数据到硬盘  
    if fsyncByPolicy(force, redisServer.aof_fsync):  
        fsync(redisServer.aof_fd) 

更多细节请看: aof.c/flushAppendOnlyFile函数 (ps: 这个函数代码看起来比较晦涩)

看到这里,你也许会有两个疑问:
1. 为什么要调用fsync函数,不是已经调用write把数据写入到文件了吗?
2. 伪代码中aof_fsync是什么,它有几种类型?

首先回答问题1,为什么写入文件后,还要调用fsync函数:
大多数unix系统为了减少磁盘IO,采用了“延迟写”技术,也就是说当我们执行完write调用后,数据并不一定立马被写入磁盘(可能还是保留在系统的buffer cache或者page cache中),这样当主机突然断电,这些我们本以为已经写入到磁盘文件的数据可能就会丢失;所以当我们需要确保数据被完整正确的写入磁盘(譬如数据库的持久化),则需要调用同步函数fsync,它会一直阻塞直到数据全部被写入到硬盘

问题2,aof_fysnc是什么:
aof_fsync用来指定flush策略,也就是调用fsync函数的策略,它一共有三种:
a. AOF_FSYNC_NO :每次都会把aof_buf中的内容写入到磁盘,但是不会调用fsync函数;
b. AOF_FSYNC_ALWAYS :每次都会把aof_buf中的内容写入到磁盘,同时调用fsync函数;
c. AOF_FSYNC_EVERYSEC

由于AOF_FSYNC_ALWAYS每次都写入文件都会调用fsync,所以这种flush策略可以保证数据的完整性,缺点就是性能太差(因为fysnc是个同步调用,会阻塞主进程对客户端请求的处理),而AOF_FSYNC_NO由于依赖于操作系统自动sync,因此不能保证数据的完整性;

那有没有一种折中的方式:既能不过分降低系统的性能,又能最大程度上的保证数据的完整性,答案就是:AOF_FSYNC_EVERYSEC,AOF_FSYNC_EVERYSEC的flush策略是:定期(至少1s)去调用fsync,并且该操作是放到一个异步队列中(线程)去执行,因此不会阻塞主进程

AOF模式至此我们已经基本说完,但是随着Redis运行,AOF文件会变得越来越大(在业务高分期增长的更快),原因有两个
a. AOF协议本身是文本协议,比较占空间;
b. Redis需要记录从开始到现在的所有更新命令;

这两个原因导致了AOF文件容易变得很大,那有什么方式可以优化吗?譬如用户执行了三个命令:lpush name diaocow; lpush name jack; lpush name jobs
AOF文件会记录以下数据:
*3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$7\r\ndiaocow
*3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$4\r\njack
*3\r\n$5\r\nlpush\r\n$4\r\nname\r\n$4\r\njobs

但其实只需要记录一条:lpush name diaocow jack jbos 命令即可:
*5\r\n$5\r\nlpush\r\n$4\r\nname\r\n$7\r\ndiaocow\r\n$4\r\njack\r\n$4\r\njobs

所以当AOF文件达到 REDIS_AOF_REWRITE_MIN_SIZE(1M)时,Redis就会执行AOF_REWRITE来优化AOF文件;

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

AOF_REWRITE触发条件:
1. 被动: 当AOF文件尺寸超过REDIS_AOF_REWRITE_MIN_SIZE & 达到一定增长比;
2. 主动: 调用BGREWRITEAOF命令;

主动和被动方式的AOF_REWRITE过程基本相同,唯一的区别就是,通过BGREWRITEAOF命令执行的AOF_REWRITE(主动)是在一个子进程中进行,因此它不会阻塞主进程对客户端请求的处理,而被动方式由于是在主进程中进行,所以在AOF_REWRITE过程中redis是无法响应客户端请求的;

下面我就以BGREWRITEAOF命令为例,具体看下AOF_REWRITE过程:


整个AOF_WRITE过程,最重要的一个函数是: rewriteAppendOnlyFile,它主要做了下面事情:
a. 创建一个临时文件temp-rewriteaof-pid.aof;
b. 循环所有数据库,把每一个数据库中的键值对,按照aof协议写入到临时文件;
c. 重命名临时文件;

伪代码:
def rewriteAppendOnlyFile(filename):
    # 创建临时文件
    tempFile = createTempFile():
    # 循环所有数据库
    for db in redisServer.dbs:
        # 把数据库中的每一个键值对,按照AOF协议写入临时文件
        for key, val in db.key_value_pairs():
            expired_time = getExpiredTime(key)
            # 过滤过期键
            if expired_time != -1 and expired_time < now_time:
                continue
            # 获取键值对应的命令(譬如 string->set,list->lpush)
            cmd = getCmdByValueType(val)
            # 按照aof协议保存键值对
            saveIntoAofProcotol(cmd, key,value, tempFile)
            # 若该键关联一个过期时间,并且未超时,则写入该键的过期信息
            if expired_time > now_time:
                 saveIntoAofProcotol("pexpiredat", key, expired_time, tempFile)
    # 重命名文件
    rename(tempFile, filename)

当AOF_REWRITE过程执行完毕,Redis会用新生成的文件去替换原来的AOF文件,至此我们可以说,现在AOF文件中的内容已经是最精简的了

现在还存在一个问题:如果我们是通过主动方式去执行AOF_REWRITE,那么在保存AOF文件期间,“键空间”是可能发生变化的(因为主进程没有被阻塞),若直接用新生成的文件去替换原来的AOF文件,就会造成数据的不一致性(丢失在AOF_REWRITE过程中更新的数据)

那redis如何解决这个问题呢? 在文章开头讲AOF模式的时候,我列举了下面一段伪代码:
def processCommand(cmd, argc, argv):
    # 执行命令
    call(cmd, argc, argv)
    # 该命令变更了键空间并且AOF模式打开
    if redisServer.update_key_space and redisServer.aof_state & REDIS_AOF_ON:
        feedAppendOnlyFile(cmd, argc, argv) 

def feedAppendOnlyFile(cmd, argc, argv):
    # 把命令转换成AOF协议格式
    aofCmdStr = getAofProtocolStr(cmd, argc, argv)
    redisServer.aof_buf.append(aofCmdStr )

    # 存在一个子进程正在进行AOF_REWRITE
    if redisServer.aof_child_pid != -1:
        # 把变更命写写到aof重写缓存
        redisServer.aof_rewrite_buf_blocks.append(aofCmdStr )

你会发现,如果redis检测到有一个子进程正在进行AOF_REWRITE,那么它会把这期间所有变更命令写到AOF重写缓存(aof_rewrite_buf_blocks),然后当子进程完成AOF_REWRITE后,它会再把AOF重写缓存中的内容追加到新生成文件,这样我们就可以保证数据的一致性,避免刚才说的问题发生;

总结:
1. 了解AOF模式作用及原理
2. 了解AOF重写作用及原理
3. 了解AOF协议
  • 大小: 18.1 KB
  • 大小: 19.3 KB
分享到:
评论

相关推荐

    Redis全套学习笔记 (带章节目录) 完整版pdf

    本文是一篇关于Redis全套学习笔记的文章,主要介绍了Redis的基础知识、数据结构、持久化、集群、高可用、性能优化等方面的内容。通过本文的学习,读者可以全面掌握Redis的使用和应用,提高自己的技术水平和实践能力...

    Redis全套学习笔记 完整版pdf.rar

    Redis全套学习笔记 完整版pdf.rar set:添加键值对 get:获取值 apend:追价值 strlen:获取值的长度 setnx:key不存在时,设置key的值 incr:原子递增1 decr:原子递减1 incrby/decrby:递增或者递减指定的数字 ...

    Redis全套学习笔记-带章节目录-114页.pdf

    Redis全套学习笔记-带章节目录

    redis-check-aof.exe

    redis-check-aof.exe-

    redis的学习笔记 redis.pdf

    redis的学习笔记

    Redis学习笔记.pdf

    Redis学习笔记.pdf 含目录 #资源达人分享计划#

    尚硅谷——Redis之RDB和AOF持久化

    RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。 也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

    redis源码学习

    redis源码学习,分享给大家,希望对大家有用

    Redis学习笔记整理

    二、 redis学习笔记之数据类型 3 三、 redis学习笔记之排序 11 四、 redis学习笔记之事务 16 五、 redis学习笔记之pipeline 20 六、 redis学习笔记之发布订阅 23 七、 redis学习笔记之持久化 28 八、 redis学习笔记...

    redis/phpredis源码及文档

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 附件里面包括redis源码,phpredis源码,redis指令及文档

    redis相关学习笔记

    redis相关学习笔记

    Redis全套学习笔记

    Redis全套学习笔记,分享的是整理的《Redis 全套学习笔记.pdf》,图文并茂,特别详细,基本上涵盖了 Redis 所有知识点。redis是一个高性能的key-value数据库,它是完全开源免费的,而且redis是一个NOSQL类型数据库,...

    Redis源码学习-Redis源码+电子书+PPT+PDF

    学习Redis源码和资料,提供完整的信息供学习.《Redis 源码日志》Redis 事件驱动内部有四个主要的数据结构,分别是:事件循环结构体,文件事件结构体,时间事件结构体和触发事件结构体。

    redis学习笔记

    redis学习笔记redis 是一个开源的 key-value 数据库。它又经常被认为是一个数据结构服务器。 因为它的 value 不仅包括基本的 string 类型还有 list,set ,sorted set 和 hash 类型。当 然这些类型的元素也都是 string...

    redis详细学习笔记

    redis详细学习笔记

    redis cmd 学习笔记

    redis cmd 学习笔记

    Redis原理学习笔记

    Redis原理学习笔记

    redis源码分析PDF

    redis源码级别的分析。需要具备c语言基础的开发学习,十分详细,带你全程深入了解redis。

    redis源码阅读笔记(6)——ziplist

    NULL 博文链接:https://xpenxpen.iteye.com/blog/2090620

Global site tag (gtag.js) - Google Analytics