redis源码分析(四)--aof持久化

Redis aof持久化

Redis支持两种持久化方式:rdb与aof,上一篇文章中已经大致介绍了rdb的持久化实现,这篇文章主要介绍aof实现。

与rdb方式相比,aof会使用更多的存储空间,因为它将数据以客户端命令的形式进行存储,并使用ascii编码。但它也有相应的优点,如支持append的方式保存db内容的变动,不需要像rdb方式一样一旦内容有变动,便需要重新完整生成文件才能将变动保存到文件中;同时在子进程持久化的过程中,可以累积客户端的命令到缓存中,最后将缓存内容添加到持久化生成的文件的末尾,几乎可以实现不丢失内容的持久化。

1. aof命令格式

aof的持久化方式不仅可以将client端发送来的命令直接添加到aof文件的末尾,还可以将内存中的数据重写为命令的形式。redis中定义aof中的一条完整命令格式如下:

*count\r\n{$len\r\ncontent\r\n}

以*开头,后面接这条命令中的参数数目count,并以\r\n结束;后面的每一个参数都以$开头,接参数长度len,并以\r\n结束,后面跟实际的参数内容,并以\r\n结束。

举例,命令RPUSH “key1” 1 2 3 4这条命令在aof文件中的表示如下:

*6\r\n$5\r\nRPUSH\r\n$4\r\nkey1\r\n\$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n$1\r\n4\r\n

这表示命令中有6个参数,第1个参数长度为5,值为RPUSH,第2个参数长度为4,值为key1,以此类推。

而命令 set “key2” “hello, world”这条命令在aof文件中表示如下:

*3\r\n$3\r\nset\r\n$4\r\nkey2\r\n$12\r\nhello, world\r\n

2. db中的数据rewrite

对于已经存储在db中的数据,如果需要以aof的方式进行持久化,那么需要将其重写为命令的形式,这个功能通过aof.c源文件中的rewriteAppendOnlyFileRio函数实现。它会遍历所有的db字典,并遍历每一个字典中的所有key-value对,进行rewrite。重写规则大致如下:

  1. 遍历每一个db,首先添加一条命令"*2\r\n$6\r\nSELECT\r\n$len\r\nj\r\n",其中的len为db索引的字符串形式的长度,j为其字符串表示,每一个db仅在遍历重写它的key-value对之前添加该命令。
  2. 遍历每一对key-value对,根据其类型,添加正确的命令头,一条命令尽可能多的添加数据,但一条命令中参数个数有限制,超过限制则拆分为多条命令。

举例,如果内存中存在一个”name1” “faker”的key-value对,重写命令如下:

*3\r\n$3\r\nset\r\n$5\r\nname\r\n$5\r\nfaker\r\n

如果内存中存在一个list,key为”key1”,内容为1 2 3 4,那么其重写后的命令如下:

*6\r\n$5\r\nRPUSH\r\n$4\r\nkey1\r\n\$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n$1\r\n4\r\n

3. 命令缓存

redis中aof持久化使用到了两类缓存,一类缓存用于在子进程运行过程中,保存客户端的命令,它是server全局结构的一个list成员aof_rewrite_buf_blocks,该list的节点值类型为

typedef struct aofrwblock {
    unsigned long used, free;
    char buf[AOF_RW_BUF_BLOCK_SIZE];
} aofrwblock;

当需要将命令保存到aof文件中,而此时server.aof_child_pid != -1(即aof子进程正在运行),命令被添加到aof_rewrite_buf_blocks链接的缓存中。

这个buffer中的数据会通过pipe发送给子进程,发送函数为aofChildWriteDiffData,这个函数在pipe的写事件发生时调用。相应的子进程中会有从pipe接收这些缓存数据的函数aofReadDiffFromParent,这个函数在子进程持久化数据的过程中被主动调用,并将接收的数据保存到server. aof_child_diff中,在内存数据处理完成后,添加到aof文件末尾。

另一类缓存是server.aof_buf,这是一个sds类型的缓存,它在aof持久化开启,并且没有aof子进程运行时使用,客户端命令始终首先保存到该缓存中,然后周期性将该缓存添加到aof文件中。

通过缓存命令的方式,保证了aof持久化不会丢失更新。

4. aof创建流程

一个aof持久化文件的完整创建流程如下:

  1. rewriteAppendOnlyFileBackground启动子进程将db字典中的数据持久化,即使是以aof方式持久化,仍然可以选择将此时db字典中的数据以rdb的方式进行存储,这部分数据恢复时当然也是调用rdb相应函数。
  2. 等待db字典中的数据持久化完成,将持久化过程中子进程接收的aof_child_diff添加到aof文件的末尾。
  3. 父进程将仍未发送给子进程的aof_rewrite_buf_blocks中的累计更新添加到aof文件末尾。初始化server.aof_buf缓存。
  4. 客户端命令被缓存到server.aof_buf末尾,并定期更新到aof文件中。

生成一个有效的aof文件后,后续db字典中的数据有变动时,只需要将相应的命令添加到aof文件末尾,即可完成相应的持久化,不需要像rdb一样为了保存新的改动,必须重新完整地对db字典进行处理。

aof文件的载入同样相对简单,按行读取,从*后得到参数数目,然后读取指定数目的参数后,执行命令。

原文地址:https://www.cnblogs.com/yang-zd/p/11629003.html

时间: 2024-08-03 16:06:55

redis源码分析(四)--aof持久化的相关文章

redis源码分析3---结构体---字典

redis源码分析3---结构体---字典 字典,简单来说就是一种用于保存键值对的抽象数据结构: 注意,字典中每个键都是独一无二的:在redis中,内部的redis的数据库就是使用字典作为底层实现的: 1 字典的实现 在redis中,字典是使用哈希表作为底层实现的,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存了字典中的一个键值对: hash表定义 table属性是一个数组,数组中的每个元素都是一个指向dictEntry结构的指针,每个dictEntry结构保存着一个键值

redis源码分析之事务Transaction(下)

接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需要先对上面文章有所了解: redis源码分析之事务Transaction(上) 一.redis事务核心命令简介 redis事务操作核心命令: //用于开启事务 {"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0}, //用来执行事

redis源码分析4---结构体---跳跃表

redis源码分析4---结构体---跳跃表 跳跃表是一种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的: 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.性能上和平衡树媲美,因为事先简单,常用来代替平衡树. 在redis中,只在两个地方使用了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构. 1 跳跃表节点 1.1 层 层的数量越多,访问其他节点的速度越快: 1.2 前进指针 遍历举例

redis 源码分析(一) 内存管理

一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是非常重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,其对应的源码在src/zmalloc.h和src/zmalloc.c两个文件中,源码点这里. 二,redis内存管理源码分析 redis封装是为了屏蔽底层平台的差异,同时方便自己实现相关的函数,我们可以通过src/zmalloc.h 文件中的相关宏定义

baksmali和smali源码分析(四)

baksmali 首先执行的第一个main 函数     public static void main(String[] args) throws IOException {         Locale locale = new Locale("en", "US");         Locale.setDefault(locale);         CommandLineParser parser = new PosixParser();         C

Nouveau源码分析(四):NVIDIA设备初始化之nouveau_drm_load (1)

Nouveau源码分析(四) probe函数成功返回之后,DRM模块就会调用struct drm_driver的load函数,对应nouveau的nouveau_drm_load. 这个函数虽然看起来不是特别长,但每一个调用的函数展开后就会变得非常长了! // /drivers/gpu/drm/nouveau/nouveau_drm.c 364 static int 365 nouveau_drm_load(struct drm_device *dev, unsigned long flags)

redis源码分析之内存布局

redis源码分析之内存布局 1. 介绍 众所周知,redis是一个开源.短小.高效的key-value存储系统,相对于memcached,redis能够支持更加丰富的数据结构,包括: 字符串(string) 哈希表(map) 列表(list) 集合(set) 有序集(zset) 主流的key-value存储系统,都是在系统内部维护一个hash表,因为对hash表的操作时间复杂度为O(1).如果数据增加以后,导致冲突严重,时间复杂度增加,则可以对hash表进行rehash,以此来保证操作的常量时

mybatis源码分析(四) mybatis与spring事务管理分析

mybatis源码分析(四) mybatis与spring事务管理分析 一丶从jdbc的角度理解什么是事务 从mysql获取一个连接之后, 默认是自动提交, 即执行完sql之后, 就会提交事务. 这种事务的范围是一条sql语句. 将该连接设置非自动提交, 可以执行多条sql语句, 然后由程序决定是提交事务, 还是回滚事务. 这也是我们常说的事务. Connection connection = dataSource.getConnection(); // connection.setTransa

Redis源码分析(四)-- sds字符串

今天分析的是Redis源码中的字符串操作类的代码实现.有了上几次的分析经验,渐渐觉得我得换一种分析的方法,如果每个API都进行代码分析,有些功能性的重复,导致分析效率的偏低.所以下面我觉得对于代码的分析偏重的是一种功能整体的思维实现来讲解,其中我也会挑出一个比较有特点的方法进行拆分了解,这也可以让我们见识一下里面的一些神奇的代码.好,回归正题,说到字符串,这不管放到哪个编程语言中,都是使用频率极高的操作类.什么new String, concat, strcopy,substr, splitSt